package dev.photosync.platform.impl;

import dev.photosync.mcapi.screen.ScreenModel;
import lombok.Getter;
import lombok.experimental.Accessors;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.input.CharacterEvent;
import net.minecraft.client.input.KeyEvent;
import net.minecraft.client.input.MouseButtonEvent;
import net.minecraft.network.chat.Component;

/**
 * The one vanilla {@link Screen} this mod owns; every PhotoSync screen is a
 * {@link ScreenModel} wearing it.
 *
 * <p>Nothing is delegated to {@code super} except key handling, which is where
 * Escape lives. In particular vanilla's {@code renderBackground} is not called:
 * the model draws its own scrim and panel, and vanilla's would paint over the
 * world underneath at the wrong moment.
 */
public final class ModelScreen extends Screen {

    @Getter
    @Accessors(fluent = true)
    private final ScreenModel model;

    ModelScreen(ScreenModel model) {
        super(Component.literal(model.title()));
        this.model = model;
    }

    @Override
    protected void init() {
        model.layout(width, height);
    }

    @Override
    public void tick() {
        model.tick();
    }

    @Override
    public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) {
        model.render(new RenderAdapter(graphics, width, height, partialTick), mouseX, mouseY);
    }

    /**
     * 1.21.11 bundled the loose input arguments into records. The bridge keeps
     * the loose form -- it is the shape eight of the nine buckets speak -- so
     * this is where they get unpacked.
     *
     * <p>{@code doubleClick} is dropped: PhotoSync has no double-click gesture,
     * and the second click of a pair arrives here as an ordinary one anyway.
     */
    @Override
    public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
        return model.mouseClicked(event.x(), event.y(), event.button())
                || super.mouseClicked(event, doubleClick);
    }

    @Override
    public boolean mouseReleased(MouseButtonEvent event) {
        return model.mouseReleased(event.x(), event.y(), event.button())
                || super.mouseReleased(event);
    }

    @Override
    public boolean mouseDragged(MouseButtonEvent event, double deltaX, double deltaY) {
        return model.mouseDragged(event.x(), event.y(), event.button(), deltaX, deltaY)
                || super.mouseDragged(event, deltaX, deltaY);
    }

    /**
     * 1.20.2 split scrolling into two axes. The bridge only has one, because a
     * horizontal wheel is not something any PhotoSync screen reacts to, so the
     * vertical delta is the one that gets through.
     */
    @Override
    public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
        return model.mouseScrolled(mouseX, mouseY, scrollY)
                || super.mouseScrolled(mouseX, mouseY, scrollX, scrollY);
    }

    @Override
    public boolean keyPressed(KeyEvent event) {
        return model.keyPressed(event.key(), event.scancode(), event.modifiers())
                || super.keyPressed(event);
    }

    /**
     * The event carries a code point rather than a char, so anything outside the
     * basic plane arrives as a surrogate pair and reaches the model as two
     * chars -- which is what a {@code String} would have held anyway.
     */
    @Override
    public boolean charTyped(CharacterEvent event) {
        boolean handled = false;
        for (char character : Character.toChars(event.codepoint())) {
            handled |= model.charTyped(character, event.modifiers());
        }
        return handled || super.charTyped(event);
    }

    @Override
    public boolean isPauseScreen() {
        return model.pausesGame();
    }

    @Override
    public boolean shouldCloseOnEsc() {
        return model.closeOnEscape();
    }

    @Override
    public void removed() {
        model.closed();
    }
}
