package io.xpipe.app.core;

import io.xpipe.app.browser.BrowserFullSessionComp;
import io.xpipe.app.browser.BrowserFullSessionModel;
import io.xpipe.app.comp.BaseRegionBuilder;
import io.xpipe.app.comp.base.ModalButton;
import io.xpipe.app.comp.base.ModalOverlay;
import io.xpipe.app.core.window.AppDialog;
import io.xpipe.app.hub.comp.StoreLayoutComp;
import io.xpipe.app.hub.comp.StoreViewState;
import io.xpipe.app.platform.LabelGraphic;
import io.xpipe.app.platform.PlatformThread;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.prefs.AppPrefsComp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.terminal.TerminalDockHubManager;
import io.xpipe.app.update.AppDistributionType;
import io.xpipe.app.util.*;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;

import lombok.*;
import lombok.experimental.NonFinal;
import lombok.extern.jackson.Jacksonized;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

@Getter
public class AppLayoutModel {

    private static AppLayoutModel INSTANCE;

    private final SavedState savedState;

    private final List<Entry> entries;

    private final Property<Entry> selected;

    private final ObservableList<QueueEntry> queueEntries;

    private final BooleanProperty ptbAvailable = new SimpleBooleanProperty();

    public AppLayoutModel(SavedState savedState) {
        this.savedState = savedState;
        this.entries = createEntryList();
        this.selected = new SimpleObjectProperty<>(entries.getFirst());
        this.queueEntries = FXCollections.observableArrayList();
    }

    public static AppLayoutModel get() {
        return INSTANCE;
    }

    public static void init() {
        var state = AppCache.getNonNull("layoutState", SavedState.class, () -> new SavedState(270, 300));
        INSTANCE = new AppLayoutModel(state);
        INSTANCE.addListeners();
    }

    private void addListeners() {
        getSelected().addListener((c, o, n) -> {
            if (o != null && o.equals(getEntries().get(2))) {
                var prefs = AppPrefs.get();
                if (prefs != null) {
                    prefs.save();
                }
                var storage = DataStorage.get();
                if (storage != null) {
                    ThreadHelper.runAsync(() -> {
                        storage.refreshEntries();
                        storage.saveAsync();
                    });
                }

                if (AppPrefs.get() != null
                        && AppPrefs.get().getRequiresRestart().get()) {
                    GlobalTimer.delay(
                            () -> {
                                var modal = ModalOverlay.of(
                                        "prefsRestartTitle", AppDialog.dialogTextKey("prefsRestartContent"));
                                modal.addButton(ModalButton.cancel());
                                modal.addButton(new ModalButton("restart", () -> AppRestart.restart(), true, true));
                                modal.show();
                            },
                            Duration.ofSeconds(1));
                    AppPrefs.get().getRequiresRestart().set(false);
                }
            }

            if (o != null && o.equals(getEntries().get(0))) {
                var svs = StoreViewState.get();
                if (svs != null) {
                    svs.triggerStoreListUpdate();
                }
            }
        });
    }

    public static void reset() {
        if (INSTANCE == null) {
            return;
        }

        AppCache.update("layoutState", INSTANCE.savedState);
        INSTANCE = null;
    }

    public synchronized void showQueueEntry(QueueEntry entry, Duration duration, boolean allowDuplicates) {
        if (!allowDuplicates && queueEntries.contains(entry)) {
            return;
        }

        queueEntries.add(entry);
        if (duration != null) {
            GlobalTimer.delay(
                    () -> {
                        synchronized (this) {
                            queueEntries.remove(entry);
                        }
                    },
                    duration);
        }
    }

    public void selectBrowser() {
        PlatformThread.runLaterIfNeeded(() -> {
            selected.setValue(entries.get(1));
        });
    }

    public void selectSettings() {
        PlatformThread.runLaterIfNeeded(() -> {
            selected.setValue(entries.get(2));
        });
    }

    public void selectLicense() {
        PlatformThread.runLaterIfNeeded(() -> {
            selected.setValue(entries.get(3));
        });
    }

    public void selectConnections() {
        PlatformThread.runLaterIfNeeded(() -> {
            selected.setValue(entries.getFirst());
        });
    }

    private List<Entry> createEntryList() {
        var l = new ArrayList<>(List.of(
                new Entry(
                        AppI18n.observable("connections"),
                        new LabelGraphic.IconGraphic("mdi2c-connection"),
                        new StoreLayoutComp(),
                        () -> {
                            TerminalDockHubManager.get().hideDock();
                        },
                        new KeyCodeCombination(KeyCode.DIGIT1, KeyCombination.SHORTCUT_DOWN)),
                new Entry(
                        AppI18n.observable("browser"),
                        new LabelGraphic.IconGraphic("mdi2f-file-cabinet"),
                        new BrowserFullSessionComp(BrowserFullSessionModel.DEFAULT),
                        null,
                        new KeyCodeCombination(KeyCode.DIGIT2, KeyCombination.SHORTCUT_DOWN)),
                new Entry(
                        AppI18n.observable("settings"),
                        new LabelGraphic.IconGraphic("mdsmz-miscellaneous_services"),
                        new AppPrefsComp(),
                        null,
                        new KeyCodeCombination(KeyCode.DIGIT3, KeyCombination.SHORTCUT_DOWN)),
                new Entry(
                        AppI18n.observable("explorePlans"),
                        new LabelGraphic.IconGraphic("mdi2p-professional-hexagon"),
                        LicenseProvider.get().overviewPage(),
                        null,
                        null),
                new Entry(
                        AppI18n.observable("docs"),
                        new LabelGraphic.IconGraphic("mdi2b-book-open-variant"),
                        null,
                        () -> Hyperlinks.open(DocumentationLink.getRoot()),
                        null),
                new Entry(
                        AppI18n.observable("visitGithubRepository"),
                        new LabelGraphic.IconGraphic("mdi2g-github"),
                        null,
                        () -> Hyperlinks.open(Hyperlinks.GITHUB),
                        null),
                new Entry(
                        AppI18n.observable("discord"),
                        new LabelGraphic.IconGraphic("bi-discord"),
                        null,
                        () -> Hyperlinks.open(Hyperlinks.DISCORD),
                        null)));
        if (AppDistributionType.get() != AppDistributionType.WEBTOP) {
            l.add(new Entry(
                    AppI18n.observable("webtop"),
                    new LabelGraphic.IconGraphic("mdal-desktop_mac"),
                    null,
                    () -> Hyperlinks.open(Hyperlinks.GITHUB_WEBTOP),
                    null));
        }
        return l;
    }

    @Data
    @Builder
    @Jacksonized
    public static class SavedState {

        double sidebarWidth;
        double browserConnectionsWidth;
    }

    public record Entry(
            ObservableValue<String> name,
            LabelGraphic icon,
            BaseRegionBuilder<?, ?> comp,
            Runnable action,
            KeyCombination combination) {}

    @Value
    @NonFinal
    @AllArgsConstructor
    public static class QueueEntry {

        public static QueueEntry ofNotification(String nameKey, String icon) {
            return new QueueEntry(AppI18n.observable(nameKey), new LabelGraphic.IconGraphic(icon), () -> true);
        }

        public static QueueEntry ofNotification(String nameKey, String modalKey, String icon, boolean hideOnClick) {
            return new QueueEntry(AppI18n.observable(nameKey), new LabelGraphic.IconGraphic(icon), () -> {
                AppDialog.information(modalKey);
                return hideOnClick;
            });
        }

        public QueueEntry(ObservableValue<String> name, LabelGraphic icon, Supplier<Boolean> action) {
            this.name = name;
            this.icon = icon;
            this.action = action;
            this.top = false;
        }

        ObservableValue<String> name;
        LabelGraphic icon;
        Supplier<Boolean> action;
        boolean top;

        public void execute() {
            ThreadHelper.runAsync(() -> {
                executeSync();
            });
        }

        public void executeSync() {
            try {
                var r = getAction().get();
                if (r) {
                    PlatformThread.runLaterIfNeeded(() -> {
                        AppLayoutModel.get().getQueueEntries().remove(this);
                    });
                }
            } catch (Throwable t) {
                PlatformThread.runLaterIfNeeded(() -> {
                    AppLayoutModel.get().getQueueEntries().remove(this);
                });
                throw t;
            }
        }

        public void hide() {
            AppLayoutModel.get().getQueueEntries().remove(this);
        }
    }
}
