package dev.photosync.ui.screen;

import dev.photosync.core.provider.Album;
import dev.photosync.core.provider.PhotoProvider;
import dev.photosync.core.provider.ProviderException;
import dev.photosync.mcapi.Keys;
import dev.photosync.mcapi.render.RenderBridge;
import dev.photosync.ui.PhotoSyncUi;
import dev.photosync.ui.Rect;
import dev.photosync.ui.widget.Button;
import dev.photosync.ui.widget.ScrollModel;
import dev.photosync.ui.widget.TextField;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

/**
 * Where uploads land: one album, or the whole library.
 *
 * <p>A separate screen rather than a dropdown in the settings form, because the
 * list comes off the network and can be long, empty, or an error -- three states
 * a dropdown has nowhere to put. It is reached from the settings screen and
 * returns to it, so it wears the settings tab.
 *
 * <p>Picking writes straight through to the saved config instead of the draft.
 * The player did not type this value, they chose it from the server's own list;
 * making them press Save afterwards would be asking them to confirm something
 * they already confirmed by clicking.
 */
public final class AlbumPickerScreen extends PhotoSyncScreen {

    private static final int ROW_HEIGHT = 20;

    private final ScrollModel scroll;
    private final TextField newAlbumName;

    private Rect listArea = Rect.EMPTY;
    private Button back;
    private Button create;

    private volatile List<Album> albums = List.of();
    private volatile String error = "";
    private volatile boolean loading;
    private boolean requested;

    public AlbumPickerScreen(PhotoSyncUi ui) {
        super(ui);
        this.scroll = new ScrollModel(ui.chrome());
        this.newAlbumName = new TextField(chrome, ui.bridge().clipboard(), value -> {
        });
        this.newAlbumName.hint(chrome.translate("photosync.album.new_hint"));
        this.newAlbumName.maxLength(64);
    }

    @Override
    protected Tab tab() {
        return Tab.SETTINGS;
    }

    @Override
    protected void layoutBody(Rect area) {
        int gap = theme().gap();
        int control = theme().controlHeight();

        Rect actions = area.bottom(control);
        Rect creation = area.dropBottom(control + gap).bottom(control);
        listArea = area.dropBottom((control + gap) * 2);
        scroll.viewport(listArea);

        int createWidth = 64;
        // The text field is added first so a click on the button, which overlaps
        // nothing, still reaches it -- the list hit-tests by rectangle either way.
        newAlbumName.bounds(creation.dropRight(createWidth + gap));
        widgets.add(newAlbumName);
        create = widgets.add(new Button(chrome, chrome.translate("photosync.album.create"), this::createAlbum));
        create.bounds(creation.right(createWidth));

        back = widgets.add(new Button(chrome, chrome.translate("photosync.album.back"),
                () -> ui.open(Tab.SETTINGS)));
        back.bounds(actions.left(80));

        Button refresh = widgets.add(new Button(chrome, chrome.translate("photosync.album.refresh"), this::load));
        refresh.bounds(actions.right(80));

        if (!requested) {
            requested = true;
            load();
        }
    }

    // -----------------------------------------------------------------------
    // Talking to the server
    // -----------------------------------------------------------------------

    private void load() {
        Optional<PhotoProvider> provider = ui.core().session().provider();
        if (provider.isEmpty()) {
            error = chrome.translate("photosync.album.not_configured");
            return;
        }
        loading = true;
        error = "";
        CompletableFuture.runAsync(() -> {
            try {
                List<Album> loaded = provider.get().albums();
                albums = loaded;
                ui.rememberAlbums(loaded);
            } catch (ProviderException e) {
                error = e.getMessage();
            } finally {
                loading = false;
            }
        });
    }

    private void createAlbum() {
        String name = newAlbumName.value().trim();
        Optional<PhotoProvider> provider = ui.core().session().provider();
        if (name.isEmpty() || provider.isEmpty() || loading) {
            return;
        }
        loading = true;
        error = "";
        CompletableFuture.runAsync(() -> {
            try {
                Album created = provider.get().createAlbum(name);
                List<Album> merged = new ArrayList<>(albums);
                merged.add(created);
                albums = List.copyOf(merged);
                ui.rememberAlbums(albums);
                // Creating an album is only ever a prelude to using it.
                ui.game().submit(() -> {
                    ui.chooseAlbum(created.id());
                    newAlbumName.reset("");
                });
            } catch (ProviderException e) {
                error = e.getMessage();
            } finally {
                loading = false;
            }
        });
    }

    // -----------------------------------------------------------------------
    // Rendering
    // -----------------------------------------------------------------------

    @Override
    protected void renderBody(RenderBridge render, int mouseX, int mouseY) {
        scroll.contentHeight((albums.size() + 1) * ROW_HEIGHT);
        scroll.advance(System.currentTimeMillis());
        create.enabled(!newAlbumName.value().isBlank() && !loading);

        chrome.well(render, listArea);
        if (!error.isEmpty()) {
            chrome.notice(render, listArea, chrome.translate("photosync.album.failed"), error);
            return;
        }

        render.pushClip(listArea.x(), listArea.y(), listArea.width(), listArea.height());
        int width = listArea.width() - scroll.gutter();
        // Index -1 is the library row, which is always offered and always first:
        // it is the one choice that cannot fail to exist.
        for (int index = -1; index < albums.size(); index++) {
            Rect row = rowBounds(index, width);
            if (row.bottom() >= listArea.y() && row.y() <= listArea.bottom()) {
                renderRow(render, index, row, mouseX, mouseY);
            }
        }
        render.popClip();

        scroll.render(render, mouseX, mouseY);
        if (loading) {
            chrome.busyBar(render, listArea.bottom(2), System.currentTimeMillis(), theme().accent());
        }
    }

    private void renderRow(RenderBridge render, int index, Rect row, int mouseX, int mouseY) {
        boolean library = index < 0;
        Album album = library ? null : albums.get(index);
        String id = library ? "" : album.id();
        boolean selected = ui.draft().albumId().equals(id);
        boolean hovered = row.contains(mouseX, mouseY) && listArea.contains(mouseX, mouseY);

        if (selected || hovered) {
            render.fill(row.x(), row.y(), row.width(), row.height(),
                    selected ? theme().fade(theme().accent(), 0.30f) : theme().surfaceHover());
        }
        if (selected) {
            render.fill(row.x(), row.y(), 2, row.height(), theme().accent());
        }

        String name = library ? chrome.translate("photosync.album.library") : album.name();
        String count = library ? "" : String.valueOf(album.assetCount());
        int countWidth = count.isEmpty() ? 0 : render.textWidth(count) + 8;
        chrome.fitted(render, chrome.elide(render, name, row.width() - 10 - countWidth),
                row.dropLeft(6).dropRight(countWidth), selected ? theme().text() : theme().textMuted());
        if (!count.isEmpty()) {
            chrome.fitted(render, count, row.dropRight(6).right(countWidth), theme().textFaint());
        }
    }

    private Rect rowBounds(int index, int width) {
        return new Rect(listArea.x(), listArea.y() + (index + 1) * ROW_HEIGHT - scroll.offset(), width, ROW_HEIGHT);
    }

    @Override
    protected String footerText() {
        if (loading) {
            return chrome.translate("photosync.album.loading");
        }
        return chrome.translate("photosync.album.status", ui.albumLabel());
    }

    // -----------------------------------------------------------------------
    // Input
    // -----------------------------------------------------------------------

    @Override
    public boolean mouseClicked(double mouseX, double mouseY, int button) {
        if (scroll.mouseClicked(mouseX, mouseY, button)) {
            return true;
        }
        if (button == 0 && listArea.contains(mouseX, mouseY) && error.isEmpty()) {
            int width = listArea.width() - scroll.gutter();
            for (int index = -1; index < albums.size(); index++) {
                if (rowBounds(index, width).contains(mouseX, mouseY)) {
                    ui.chooseAlbum(index < 0 ? "" : albums.get(index).id());
                    return true;
                }
            }
        }
        return super.mouseClicked(mouseX, mouseY, button);
    }

    @Override
    public boolean mouseReleased(double mouseX, double mouseY, int button) {
        return scroll.mouseReleased() || super.mouseReleased(mouseX, mouseY, button);
    }

    @Override
    public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
        return scroll.mouseDragged(mouseX, mouseY, button)
                || super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
    }

    @Override
    public boolean mouseScrolled(double mouseX, double mouseY, double amount) {
        return scroll.mouseScrolled(mouseX, mouseY, amount, ROW_HEIGHT)
                || super.mouseScrolled(mouseX, mouseY, amount);
    }

    /** Enter in the name field creates the album, which is what it looks like it should do. */
    @Override
    public boolean keyPressed(int key, int scanCode, int modifiers) {
        if (newAlbumName.focused() && Keys.confirms(key)) {
            createAlbum();
            return true;
        }
        return super.keyPressed(key, scanCode, modifiers);
    }

    @Override
    public boolean closeOnEscape() {
        return !widgets.hasFocus();
    }
}
