package com.minelittlepony.unicopia.client.gui.spellbook;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.IntConsumer;

import com.minelittlepony.common.client.gui.GameGui;
import com.minelittlepony.common.client.gui.IViewRoot;
import com.minelittlepony.common.client.gui.dimension.Bounds;
import com.minelittlepony.common.client.gui.element.Button;
import com.minelittlepony.common.client.gui.sprite.TextureSprite;
import com.minelittlepony.unicopia.Debug;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.client.TextHelper;
import com.minelittlepony.unicopia.client.gui.*;
import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookChapterList.*;
import com.minelittlepony.unicopia.compat.trinkets.TrinketSlotBackSprites;
import com.minelittlepony.unicopia.container.*;
import com.minelittlepony.unicopia.container.inventory.*;
import com.minelittlepony.unicopia.network.Channel;
import com.minelittlepony.unicopia.network.MsgSpellbookStateChanged;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;

import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.Drawable;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.gui.screen.recipebook.RecipeBookProvider;
import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.screen.slot.Slot;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.Util;
import net.minecraft.util.math.ColorHelper;

public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> implements RecipeBookProvider {
    public static final Identifier TEXTURE = Unicopia.id("textures/gui/container/book.png");
    public static final Identifier SLOT = Unicopia.id("textures/gui/container/slot.png");
    public static final Identifier GEM = Unicopia.id("textures/item/gemstone.png");

    private static final int CONTENT_PADDING = 30;

    public static final int TITLE_X = 30;
    public static final int TITLE_Y = 20;
    public static final int TITLE_COLOR = 0xFF404040;

    private final RecipeBookWidget recipeBook = new RecipeBookWidget();

    private final Chapter craftingChapter;
    private final SpellbookTraitDexPageContent traitDex = new SpellbookTraitDexPageContent(this);
    private final SpellbookChapterList chapters = new SpellbookChapterList(this,
        craftingChapter = new Chapter(SpellbookChapterList.CRAFTING_ID, TabSide.LEFT, 0, 0, Optional.of(new SpellbookCraftingPageContent(this))),
        new Chapter(SpellbookChapterList.PROFILE_ID, TabSide.LEFT, 1, 0, Optional.of(new SpellbookProfilePageContent(this))),
        new Chapter(SpellbookChapterList.TRAIT_DEX_ID, TabSide.LEFT, 3, 0, Optional.of(traitDex))
    );
    private final SpellbookTabBar tabs = new SpellbookTabBar(this, chapters);

    private Bounds contentBounds = Bounds.empty();

    public SpellbookScreen(SpellbookScreenHandler handler, PlayerInventory inventory, Text title) {
        super(handler, inventory, title);
        backgroundWidth = 405;
        backgroundHeight = 219;
        contentBounds = new Bounds(CONTENT_PADDING, CONTENT_PADDING, backgroundWidth - CONTENT_PADDING * 2, backgroundHeight - CONTENT_PADDING * 3 - 2);

        handler.addSlotShowingCondition(slotType -> {
            if (slotType == SlotType.INVENTORY) {
                return chapters.getCurrentChapter().content().filter(Content::showInventory).isPresent();
            }
            return chapters.getCurrentChapter() == craftingChapter;
        });
        handler.getSpellbookState().setSynchronizer(state -> {
            Channel.CLIENT_SPELLBOOK_UPDATE.sendToServer(MsgSpellbookStateChanged.create(handler, state));
        });
    }

    public SpellbookState getState() {
        return handler.getSpellbookState();
    }

    public SpellbookTraitDexPageContent getTraitDex() {
        return traitDex;
    }

    public void addPageButtons(int buttonY, int prevX, int nextX, IntConsumer pageAction) {
        addDrawableChild(new PageButton(this, x + nextX, y + buttonY, 1, pageAction));
        addDrawableChild(new PageButton(this, x + prevX, y + buttonY, -1, pageAction));
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public int getBackgroundWidth() {
        return backgroundWidth;
    }

    public int getBackgroundHeight() {
        return backgroundHeight;
    }

    public Bounds getFrameBounds() {
        return contentBounds;
    }

    @Override
    public <T extends Drawable> T addDrawable(T drawable) {
        return super.addDrawable(drawable);
    }

    @Override
    public void init() {
        super.init();
        tabs.init();
        chapters.getCurrentChapter().content().ifPresent(content -> content.init(this, chapters.getCurrentChapter().id()));
    }

    @Override
    public void refreshRecipeBook() {
        chapters.getCurrentChapter()
            .content()
            .map(i -> i instanceof RecipesChangedListener ? (RecipesChangedListener)i : null)
            .ifPresent(RecipesChangedListener::onRecipesChanged);
    }

    @Override
    public RecipeBookWidget getRecipeBookWidget() {
        return recipeBook;
    }

    @Override
    public void render(DrawContext context, int mouseX, int mouseY, float partialTicks) {
        if (getState().isDirty()) {
            clearAndInit();
        }
        super.render(context, mouseX, mouseY, partialTicks);
        drawMouseoverTooltip(context, mouseX, mouseY);
    }

    @Override
    protected void drawBackground(DrawContext context, float delta, int mouseX, int mouseY) {
        renderBackground(context);
        RenderSystem.setShaderColor(1, 1, 1, 1);

        context.drawTexture(TEXTURE, x, y, 0, 0, backgroundWidth, backgroundHeight, 512, 256);

        if (Debug.SPELLBOOK_CHAPTERS) {
            clearAndInit();
        }

        tabs.getAllTabs().forEach(tab -> {
            Bounds bounds = tab.bounds();
            boolean hover = bounds.contains(mouseX, mouseY);

            int color = tab.chapter().color() & 0xFFFFFF;

            int v = 100 + (hover ? 24 : 0);
            if (color == 0xFFFFFF || color == 0) {
                v += 48;
            } else {
                RenderSystem.setShaderColor(ColorHelper.Abgr.getRed(color) / 255F, ColorHelper.Abgr.getGreen(color) / 255F, ColorHelper.Abgr.getBlue(color) / 255F, 1);
            }

            boolean isRight = tab.chapter().side() == TabSide.RIGHT;

            context.drawTexture(TEXTURE, bounds.left, bounds.top, isRight ? 510 - bounds.width : 402, v, bounds.width, bounds.height, 512, 256);
            RenderSystem.setShaderColor(1, 1, 1, 1);

            float widthScale = bounds.width / 35F;
            int resolution = (int)(16 * widthScale);
            int iconInset = (int)(Math.min(widthScale * 2, 1) * 10);

            context.drawTexture(tab.icon().get(),
                    isRight ? bounds.left + bounds.width - resolution - iconInset : bounds.left + iconInset,
                    bounds.top + (bounds.height - resolution) / 2,
                    0, 0,
                    resolution, resolution,
                    resolution, resolution
            );

            if (bounds.contains(mouseX, mouseY)) {
                setTooltip(Text.translatable(Util.createTranslationKey("chapter", tab.chapter().id())));
            }
        });

        MatrixStack matrices = context.getMatrices();
        matrices.push();
        matrices.translate(x, y, 0);
        chapters.getCurrentChapter().content().ifPresent(content -> content.draw(context, mouseX, mouseY, (IViewRoot)this));
        matrices.pop();
    }

    @Override
    protected void drawMouseoverTooltip(DrawContext context, int x, int y) {

        context.getMatrices().push();
        context.getMatrices().translate(0, 0, 200);

        if (!(focusedSlot instanceof SpellSlot sp)) {
            super.drawMouseoverTooltip(context, x, y);
            return;
        }

        CustomisedSpellType<?> spell = sp.getSpell();
        if (spell.isEmpty()) {
            context.drawTooltip(textRenderer, Text.translatable("gui.unicopia.spellbook.empty_spell_slot"), x, y);
            return;
        }

        List<Text> tooltip = new ArrayList<>();
        tooltip.add(spell.type().getName());
        tooltip.addAll(TextHelper.wrap(Text.translatable(spell.type().getTranslationKey() + ".lore").formatted(spell.type().getAffinity().getColor()), 180).toList());


        context.drawTooltip(textRenderer, tooltip, x, y);
        context.getMatrices().pop();
    }

    void drawSlots(DrawContext context, int mouseX, int mouseY, float delta) {
        MatrixStack matrices = context.getMatrices();
        matrices.push();
        matrices.translate(x, y, 0);
        RenderSystem.setShaderColor(1, 1, 1, 1);
        RenderSystem.enableBlend();

        for (Slot slot : handler.slots) {
            if (slot.isEnabled() && slot instanceof SpellbookSlot p) {
                context.drawTexture(SLOT, slot.x - 8, slot.y - 8, 0, 0, 32, 32, 32, 32);

                if (slot.getStack().isEmpty()) {
                    Identifier foreground = p.getForegroundIdentifier();
                    if (foreground != null) {
                        if (p.isTrinket()) {
                            foreground = TrinketSlotBackSprites.getBackSprite(foreground);
                        }
                        RenderSystem.setShaderColor(1, 1, 1, p.getBackSpriteOpacity());
                        context.drawTexture(foreground, slot.x, slot.y, 0, 0, 16, 16, 16, 16);
                        RenderSystem.setShaderColor(1, 1, 1, 1);
                    }
                }

                if (slot instanceof SpellSlot sp) {
                    CustomisedSpellType<?> spell = sp.getSpell();

                    if (!spell.isEmpty()) {
                        RenderSystem.setShaderColor(1, 1, 1, 1);
                        context.getMatrices().push();
                        context.getMatrices().translate(0, 0, 260);
                        SpellIconRenderer.renderSpell(context, spell, slot.x, slot.y, 0.5F);
                        context.getMatrices().pop();
                        RenderSystem.enableBlend();
                    }
                }

                if (p.showTraits()) {
                    float weight = p.getWeight();
                    ItemTraitsTooltipRenderer.renderStackTraits(slot.getStack(), context, slot.x, slot.y, weight == 0 ? 1 : weight, delta, slot.id);
                    RenderSystem.enableBlend();
                }
            }
        }
        RenderSystem.disableBlend();
        RenderSystem.setShaderColor(1, 1, 1, 1);
        matrices.pop();
    }

    @Override
    protected void drawForeground(DrawContext context, int mouseX, int mouseY) {
    }

    @Override
    public boolean mouseClicked(double mouseX, double mouseY, int button) {
        return tabs.getAllTabs().anyMatch(tab -> {
            if (tab.bounds().contains(mouseX, mouseY) && chapters.getCurrentChapter() != tab.chapter()) {
                getState().setCurrentPageId(tab.chapter().id());
                GameGui.playSound(USounds.Vanilla.ITEM_BOOK_PAGE_TURN);
                clearAndInit();
                return true;
            }

            return false;
        }) || super.mouseClicked(mouseX, mouseY, button);
    }

    @Override
    protected void clearAndInit() {
        super.clearAndInit();
    }

    @Override
    public boolean mouseReleased(double mouseX, double mouseY, int button) {
        return hoveredElement(mouseX, mouseY).filter((element) -> {
            setDragging(false);
            return element.mouseReleased(mouseX, mouseY, button);
        }).isPresent() || super.mouseReleased(mouseX, mouseY, button);
    }

    @Override
    public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
        return (getFocused() != null && isDragging() && button == 0 && getFocused().mouseDragged(mouseX, mouseY, button, deltaX, deltaY))
            || super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
    }

    private static class PageButton extends ImageButton {
        private final int increment;
        private final TextureSprite sprite = new TextureSprite()
                .setSize(25, 13)
                .setTextureSize(512, 256)
                .setTextureOffset(0, 479)
                .setTexture(TEXTURE);

        public PageButton(SpellbookScreen screen, int x, int y, int increment, IntConsumer pageAction) {
            super(x, y, 25, 20);
            this.increment = increment;
            getStyle().setIcon(sprite);
            onClick(sender -> {
                pageAction.accept(increment);
                screen.clearAndInit();
            });
        }

        @Override
        public void renderButton(DrawContext context, int mouseX, int mouseY, float tickDelta) {
            if (!active) {
               return;
            }

            int state = hovered ? 1 : 0;

            sprite.setTextureOffset(23 * state, (int)(479 + 6.5F - (increment * 6.5F)));
            super.renderButton(context, mouseX, mouseY, tickDelta);
        }
    }

    static class ImageButton extends Button {

        public ImageButton(int x, int y) {
            super(x, y);
        }

        public ImageButton(int x, int y, int width, int height) {
            super(x, y, width, height);
        }

        @Override
        public void renderButton(DrawContext context, int mouseX, int mouseY, float tickDelta) {
            RenderSystem.setShader(GameRenderer::getPositionTexProgram);

            RenderSystem.setShaderColor(1, 1, 1, alpha);
            RenderSystem.defaultBlendFunc();
            RenderSystem.blendFunc(
                    GlStateManager.SrcFactor.SRC_ALPHA,
                    GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA);

            if (getStyle().hasIcon()) {
                getStyle().getIcon().render(context, getX(), getY(), mouseX, mouseY, tickDelta);
            }

            RenderSystem.setShaderColor(1, 1, 1, 1);
        }
    }

    public interface RecipesChangedListener {
        void onRecipesChanged();
    }
}
