package fr.bglands.client.fabric;

import fr.bglands.client.protocol.OpenScreen;
import fr.bglands.client.protocol.UiControl;
import fr.bglands.client.protocol.UiLayout;
import fr.bglands.client.protocol.UiResult;
import fr.bglands.client.protocol.UiScene;
import fr.bglands.client.protocol.UiSceneWidget;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.input.KeyEvent;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.util.FormattedCharSequence;
import org.lwjgl.glfw.GLFW;

import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;

final class BgLandsScreen extends Screen {
    private static final int DEFAULT_DESIGN_WIDTH = 400;
    private static final int DEFAULT_DESIGN_HEIGHT = 300;
    private static final int LONG_CONTENT_LINES_PER_PAGE = 10;
    private static final Identifier DEFAULT_BACKGROUND =
            Identifier.fromNamespaceAndPath("bglands", "textures/gui/frame_main.png");
    private static final Identifier LOGO =
            Identifier.fromNamespaceAndPath("bglands", "textures/gui/logo.png");

    private final OpenScreen definition;
    private final BgLandsConnection connection;
    private final Map<String, BgLandsButton> actionButtons = new HashMap<>();
    private final Map<String, String> pendingRequests = new HashMap<>();

    private float scale;
    private int panelX;
    private int panelY;
    private int designWidth = DEFAULT_DESIGN_WIDTH;
    private int designHeight = DEFAULT_DESIGN_HEIGHT;
    private int controlPage;
    private int controlPageCount = 1;
    private int contentPage;
    private int contentPageCount = 1;
    private int contentLinesPerPage = 3;
    private List<FormattedCharSequence> genericContentLines = List.of();
    private List<UiSceneWidget> orderedSceneWidgets = List.of();
    private Component resultMessage = Component.empty();
    private int resultColor = 0xFFFFFFFF;

    BgLandsScreen(OpenScreen definition, BgLandsConnection connection) {
        super(Component.literal(stripLegacy(definition.title())));
        this.definition = definition;
        this.connection = connection;
    }

    @Override
    protected void init() {
        clearWidgets();
        actionButtons.clear();
        UiScene scene = definition.scene();
        designWidth = scene == null ? DEFAULT_DESIGN_WIDTH : scene.canvas().width();
        designHeight = scene == null ? DEFAULT_DESIGN_HEIGHT : scene.canvas().height();
        computeLayout();
        if (scene == null) {
            initGeneric();
        } else {
            initAuthored(scene);
        }
    }

    private void initAuthored(UiScene scene) {
        orderedSceneWidgets = UiLayout.orderedWidgets(scene);
        Map<String, UiControl> controls = new HashMap<>();
        definition.controls().forEach(control -> controls.put(control.id(), control));
        for (UiSceneWidget widget : orderedSceneWidgets) {
            if (!widget.kind().equals("button")) continue;
            UiControl control = controls.get(widget.controlId());
            if (control == null) continue;
            String label = widget.label().isBlank() ? control.label() : widget.label();
            BgLandsButton button = new BgLandsButton(
                    px(widget.x()), py(widget.y()), sw(widget.width()), sh(widget.height()),
                    Component.literal(stripLegacy(label)), Component.literal(stripLegacy(control.description())),
                    widget.color().isBlank() ? definition.accent() : widget.color(), widget.asset(),
                    resolveIcon(control.icon()), parseColor(widget.color(), 0xFFFFFFFF),
                    Math.max(1, Math.round(widget.fontSize() * scale)), widget.align(),
                    () -> activate(control)
            );
            button.active = !pendingRequests.containsKey(control.id());
            actionButtons.put(control.id(), addRenderableWidget(button));
        }
        if (definition.showExitAction()) {
            addRenderableWidget(new BgLandsButton(
                    px(Math.max(0, designWidth - 92)), py(Math.max(0, designHeight - 30)),
                    sw(72), Math.max(18, sh(22)), Component.literal("Fermer"),
                    Component.literal("Fermer l'interface"), "red", resolveIcon("cancel"), this::onClose
            ));
        }
    }

    private void initGeneric() {
        orderedSceneWidgets = List.of();
        genericContentLines = font.split(Component.literal(stripLegacy(definition.content())), sw(278));
        boolean longContent = genericContentLines.size() > 3;
        contentLinesPerPage = longContent ? LONG_CONTENT_LINES_PER_PAGE : 3;
        contentPageCount = UiLayout.pageCount(genericContentLines.size(), contentLinesPerPage);
        contentPage = UiLayout.clampPage(contentPage, genericContentLines.size(), contentLinesPerPage);

        int columns = Math.max(1, Math.min(4, definition.columns()));
        int rows = longContent ? 1 : 3;
        int pageSize = columns * rows;
        controlPageCount = UiLayout.pageCount(definition.controls().size(), pageSize);
        controlPage = UiLayout.clampPage(controlPage, definition.controls().size(), pageSize);
        int areaX = px(78);
        int areaY = longContent ? py(224) : py(145);
        int areaWidth = sw(288);
        int gapX = Math.max(4, sw(7));
        int gapY = Math.max(3, sh(6));
        int cellWidth = (areaWidth - gapX * (columns - 1)) / columns;
        int buttonHeight = Math.max(18, longContent ? sh(25) : sh(32));

        int from = UiLayout.pageStart(controlPage, definition.controls().size(), pageSize);
        int to = UiLayout.pageEnd(controlPage, definition.controls().size(), pageSize);
        List<UiControl> visibleControls = definition.controls().subList(from, to);
        boolean exactPage = !visibleControls.isEmpty() && visibleControls.stream().allMatch(BgLandsScreen::hasGeometry);
        for (int index = 0; index < visibleControls.size(); index++) {
            UiControl control = visibleControls.get(index);
            int column = index % columns;
            int row = index / columns;
            int requestedWidth = exactPage ? sw(control.width())
                    : control.width() == null ? cellWidth : Math.min(cellWidth, sw(control.width()));
            int requestedHeight = exactPage ? sh(control.height()) : buttonHeight;
            int x = exactPage ? px(control.x())
                    : areaX + column * (cellWidth + gapX) + (cellWidth - requestedWidth) / 2;
            int y = exactPage ? py(control.y()) : areaY + row * (buttonHeight + gapY);
            BgLandsButton button = new BgLandsButton(
                    x, y, requestedWidth, requestedHeight, Component.literal(stripLegacy(control.label())),
                    Component.literal(stripLegacy(control.description())),
                    control.color() == null || control.color().isBlank() ? definition.accent() : control.color(),
                    resolveIcon(control.icon()), () -> activate(control)
            );
            button.active = !pendingRequests.containsKey(control.id());
            actionButtons.put(control.id(), addRenderableWidget(button));
        }

        int footerY = py(270);
        int cursor = 82;
        if (contentPageCount > 1) {
            addFooterButton(cursor, footerY, "‹", "Page de texte précédente", () -> changeContentPage(-1), contentPage > 0);
            cursor += 38;
            addFooterButton(cursor, footerY, "›", "Page de texte suivante", () -> changeContentPage(1), contentPage + 1 < contentPageCount);
            cursor += 46;
        }
        if (controlPageCount > 1) {
            addFooterButton(cursor, footerY, "‹", "Page d'actions précédente", () -> changeControlPage(-1), controlPage > 0);
            cursor += 38;
            addFooterButton(cursor, footerY, "›", "Page d'actions suivante", () -> changeControlPage(1), controlPage + 1 < controlPageCount);
        }
        if (definition.showExitAction()) {
            int closeX = (contentPageCount > 1 || controlPageCount > 1) ? 292 : 165;
            addRenderableWidget(new BgLandsButton(
                    px(closeX), footerY, sw(70), Math.max(18, sh(22)), Component.literal("Fermer"),
                    Component.literal("Fermer l'interface"), "red", resolveIcon("cancel"), this::onClose
            ));
        }
    }

    private void addFooterButton(int designX, int y, String label, String description, Runnable action, boolean active) {
        BgLandsButton button = addRenderableWidget(new BgLandsButton(
                px(designX), y, sw(32), Math.max(18, sh(22)), Component.literal(label),
                Component.literal(description), "white", null, action
        ));
        button.active = active;
    }

    @Override
    public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) {
        graphics.fill(0, 0, width, height, 0xB5000000);
        if (definition.scene() == null) renderGeneric(graphics, mouseX, mouseY, delta);
        else renderAuthored(graphics, mouseX, mouseY, delta);
    }

    private void renderAuthored(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) {
        UiScene scene = definition.scene();
        renderAsset(graphics, scene.canvas().backgroundAsset(), 0, 0, scene.canvas().width(), scene.canvas().height());
        for (UiSceneWidget widget : orderedSceneWidgets) {
            if (widget.kind().equals("button")) continue;
            if (!widget.asset().isBlank()) {
                renderAsset(graphics, widget.asset(), widget.x(), widget.y(), widget.width(), widget.height());
            }
            String text = joinText(widget.label(), widget.binding());
            if (!text.isBlank()) renderAuthoredText(graphics, widget, text);
        }
        super.extractRenderState(graphics, mouseX, mouseY, delta);
        renderResultOrTooltip(graphics, designHeight - 18);
    }

    private void renderGeneric(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) {
        graphics.blit(RenderPipelines.GUI_TEXTURED, resolveBackground(definition.background()), panelX, panelY,
                0, 0, sw(DEFAULT_DESIGN_WIDTH), sh(DEFAULT_DESIGN_HEIGHT),
                DEFAULT_DESIGN_WIDTH, DEFAULT_DESIGN_HEIGHT, DEFAULT_DESIGN_WIDTH, DEFAULT_DESIGN_HEIGHT);
        graphics.blit(RenderPipelines.GUI_TEXTURED, LOGO, px(136), py(20), 0, 0,
                sw(180), sh(60), 543, 181, 543, 181);
        super.extractRenderState(graphics, mouseX, mouseY, delta);

        String title = stripLegacy(definition.title());
        int titleX = px(78) + Math.max(0, (sw(288) - font.width(title)) / 2);
        graphics.text(font, title, titleX, py(99), parseColor(definition.accent(), 0xFF63F27A), true);

        boolean longContent = genericContentLines.size() > 3;
        int start = UiLayout.pageStart(contentPage, genericContentLines.size(), contentLinesPerPage);
        int end = UiLayout.pageEnd(contentPage, genericContentLines.size(), contentLinesPerPage);
        int y = py(112);
        for (int index = start; index < end; index++) {
            FormattedCharSequence line = genericContentLines.get(index);
            int lineX = longContent ? px(83) : px(83) + Math.max(0, (sw(278) - font.width(line)) / 2);
            graphics.text(font, line, lineX, y, 0xFFE6E8ED, false);
            y += font.lineHeight + 1;
        }
        if (contentPageCount > 1) {
            graphics.text(font, "Texte " + (contentPage + 1) + "/" + contentPageCount, px(80), py(258), 0xFFB9C0CD, false);
        }
        if (controlPageCount > 1) {
            graphics.text(font, "Actions " + (controlPage + 1) + "/" + controlPageCount, px(170), py(258), 0xFFB9C0CD, false);
        }
        // Dedicated gap between body and actions: never overlap page labels,
        // navigation controls or the close button.
        renderResultOrTooltip(graphics, longContent ? 212 : 135);
    }

    private void renderAsset(GuiGraphicsExtractor graphics, String asset, int x, int y, int assetWidth, int assetHeight) {
        AssetTexture texture = assetTexture(asset);
        if (texture == null) return;
        graphics.blit(RenderPipelines.GUI_TEXTURED, texture.identifier(), px(x), py(y), 0, 0,
                sw(assetWidth), sh(assetHeight), texture.width(), texture.height(), texture.width(), texture.height());
    }

    private void renderAuthoredText(GuiGraphicsExtractor graphics, UiSceneWidget widget, String rawText) {
        float textScale = Math.max(0.35F, Math.min(3.0F, scale * widget.fontSize() / (float) font.lineHeight));
        int screenWidth = sw(widget.width());
        int screenHeight = sh(widget.height());
        int logicalWidth = Math.max(1, Math.round(screenWidth / textScale));
        List<FormattedCharSequence> lines = font.split(Component.literal(stripLegacy(rawText)), logicalWidth);
        int lineHeight = Math.max(1, Math.round(font.lineHeight * textScale));
        int maxLines = Math.max(1, screenHeight / lineHeight);
        int visibleLines = Math.min(maxLines, lines.size());
        int baseY = py(widget.y()) + Math.max(0, (screenHeight - visibleLines * lineHeight) / 2);
        for (int index = 0; index < visibleLines; index++) {
            FormattedCharSequence line = lines.get(index);
            int renderedWidth = Math.round(font.width(line) * textScale);
            int screenX = switch (widget.align()) {
                case "right" -> px(widget.x()) + Math.max(0, screenWidth - renderedWidth);
                case "center" -> px(widget.x()) + Math.max(0, (screenWidth - renderedWidth) / 2);
                default -> px(widget.x());
            };
            int screenY = baseY + index * lineHeight;
            graphics.pose().pushMatrix();
            graphics.pose().translate(screenX, screenY);
            graphics.pose().scale(textScale, textScale);
            graphics.text(font, line, 0, 0, parseColor(widget.color(), 0xFFFFFFFF), false);
            graphics.pose().popMatrix();
        }
    }

    private void renderResultOrTooltip(GuiGraphicsExtractor graphics, int designY) {
        Optional<Component> hovered = hoveredDescription();
        Component footer = hovered.orElse(resultMessage);
        int footerColor = hovered.isPresent() ? 0xFFC8CDD8 : resultColor;
        if (!footer.getString().isBlank()) {
            String text = font.plainSubstrByWidth(footer.getString(), sw(240));
            int footerX = px(100) + Math.max(0, (sw(240) - font.width(text)) / 2);
            graphics.text(font, text, footerX, py(designY), footerColor, false);
        }
    }

    @Override
    public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) {
        if (verticalAmount != 0 && contentPageCount > 1) {
            changeContentPage(verticalAmount > 0 ? -1 : 1);
            return true;
        }
        if (verticalAmount != 0 && controlPageCount > 1) {
            changeControlPage(verticalAmount > 0 ? -1 : 1);
            return true;
        }
        return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount);
    }

    @Override
    public boolean keyPressed(KeyEvent event) {
        if (contentPageCount > 1) {
            if (event.key() == GLFW.GLFW_KEY_PAGE_UP) { changeContentPage(-1); return true; }
            if (event.key() == GLFW.GLFW_KEY_PAGE_DOWN) { changeContentPage(1); return true; }
            if (event.key() == GLFW.GLFW_KEY_HOME) { setContentPage(0); return true; }
            if (event.key() == GLFW.GLFW_KEY_END) { setContentPage(contentPageCount - 1); return true; }
        }
        return super.keyPressed(event);
    }

    @Override
    public void onClose() {
        if (minecraft != null) minecraft.gui.setScreen(null);
    }

    void acceptResult(UiResult result) {
        String controlId = pendingRequests.entrySet().stream()
                .filter(entry -> entry.getValue().equals(result.requestId())).map(Map.Entry::getKey)
                .findFirst().orElse(null);
        if (controlId == null) return;
        pendingRequests.remove(controlId);
        AbstractWidget button = actionButtons.get(controlId);
        if (button != null) button.active = true;
        resultMessage = Component.literal(stripLegacy(result.message()));
        String status = result.status().toLowerCase(Locale.ROOT);
        if (status.equals("accepted")) { onClose(); return; }
        resultColor = status.equals("ok") || status.equals("success") ? 0xFF74F58C : 0xFFFF667A;
    }

    private void activate(UiControl control) {
        Optional<String> requestId = connection.activate(definition, control);
        if (requestId.isEmpty()) {
            resultMessage = Component.literal("Action indisponible : utilisation de l'interface serveur.");
            resultColor = 0xFFFFC857;
            return;
        }
        pendingRequests.put(control.id(), requestId.get());
        BgLandsButton button = actionButtons.get(control.id());
        if (button != null) button.active = false;
        resultMessage = Component.literal("Chargement…");
        resultColor = 0xFFB9C0CD;
    }

    private void changeControlPage(int delta) {
        int next = Math.max(0, Math.min(controlPage + delta, controlPageCount - 1));
        if (next != controlPage) { controlPage = next; rebuildWidgets(); }
    }

    private void changeContentPage(int delta) {
        setContentPage(Math.max(0, Math.min(contentPage + delta, contentPageCount - 1)));
    }

    private void setContentPage(int next) {
        if (next != contentPage) { contentPage = next; rebuildWidgets(); }
    }

    private Optional<Component> hoveredDescription() {
        return actionButtons.values().stream().filter(BgLandsButton::isHovered)
                .map(BgLandsButton::description).filter(description -> !description.getString().isBlank()).findFirst();
    }

    private void computeLayout() {
        float availableX = Math.max(1, width - 12) / (float) designWidth;
        float availableY = Math.max(1, height - 12) / (float) designHeight;
        scale = Math.max(0.60F, Math.min(1.50F, Math.min(availableX, availableY)));
        panelX = (width - sw(designWidth)) / 2;
        panelY = (height - sh(designHeight)) / 2;
    }

    private int px(int x) { return panelX + sw(x); }
    private int py(int y) { return panelY + sh(y); }
    private int sw(int value) { return Math.round(value * scale); }
    private int sh(int value) { return Math.round(value * scale); }

    private static Identifier resolveBackground(String raw) {
        return DEFAULT_BACKGROUND;
    }

    private static Identifier resolveIcon(String raw) {
        if (raw == null || raw.isBlank()) return null;
        String value = raw.toLowerCase(Locale.ROOT);
        if (!value.matches("(book|cancel|chest|compass|friends|game|information|leader|minigames|settings)")) {
            value = "information";
        }
        return Identifier.fromNamespaceAndPath("bglands", "textures/gui/icons/" + value + ".png");
    }

    private static AssetTexture assetTexture(String asset) {
        if (asset == null) return null;
        if (asset.equals("/assets/frame-full.png")) return new AssetTexture(DEFAULT_BACKGROUND, 400, 300);
        if (asset.equals("/assets/logo.png")) return new AssetTexture(LOGO, 543, 181);
        if (asset.matches("/assets/buttons/button_(blue|green|purple|red|white|yellow)\\.png")) {
            return new AssetTexture(Identifier.fromNamespaceAndPath("bglands", "textures/gui/" + asset.substring(8)), 120, 32);
        }
        if (asset.matches("/assets/icons/(book|cancel|chest|compass|friends|game|information|leader|minigames|settings)\\.png")) {
            return new AssetTexture(Identifier.fromNamespaceAndPath("bglands", "textures/gui/" + asset.substring(8)), 16, 16);
        }
        return null;
    }

    private static int parseColor(String raw, int fallback) {
        String value = raw == null ? "" : raw.toLowerCase(Locale.ROOT);
        if (value.matches("#[0-9a-f]{6}")) return 0xFF000000 | Integer.parseInt(value.substring(1), 16);
        if (value.contains("blue") || value.contains("cyan")) return 0xFF42D9FF;
        if (value.contains("purple") || value.contains("violet")) return 0xFFD06BFF;
        if (value.contains("yellow") || value.contains("gold")) return 0xFFFFCA28;
        if (value.contains("red")) return 0xFFFF556A;
        if (value.contains("green")) return 0xFF63F27A;
        return fallback;
    }

    private static boolean hasGeometry(UiControl control) {
        return control.x() != null && control.y() != null && control.width() != null && control.height() != null
                && control.x() <= DEFAULT_DESIGN_WIDTH && control.y() <= DEFAULT_DESIGN_HEIGHT
                && control.width() <= DEFAULT_DESIGN_WIDTH && control.height() <= DEFAULT_DESIGN_HEIGHT
                && control.x() + control.width() <= DEFAULT_DESIGN_WIDTH
                && control.y() + control.height() <= DEFAULT_DESIGN_HEIGHT;
    }

    private static String joinText(String label, String binding) {
        if (binding != null && !binding.isBlank()) return binding;
        return label == null ? "" : label;
    }

    private static String stripLegacy(String value) {
        return value == null ? "" : value.replaceAll("(?i)§[0-9A-FK-ORX]", "");
    }

    private record AssetTexture(Identifier identifier, int width, int height) {}
}
