package io.xpipe.app.browser.file;

import io.xpipe.app.browser.BrowserAbstractSessionModel;
import io.xpipe.app.browser.BrowserFullSessionModel;
import io.xpipe.app.browser.BrowserSessionTab;
import io.xpipe.app.comp.BaseRegionBuilder;
import io.xpipe.app.comp.base.ModalOverlay;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.core.window.AppDialog;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.terminal.*;
import io.xpipe.app.util.GlobalTimer;
import io.xpipe.app.util.ThreadHelper;

import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;

import java.time.Duration;
import java.util.UUID;
import java.util.function.UnaryOperator;

public final class BrowserTerminalDockTabModel extends BrowserSessionTab {

    private final BrowserSessionTab origin;
    private final ObservableList<UUID> terminalRequests;
    private final TerminalDockView dockModel = new TerminalDockView(UnaryOperator.identity());
    private final BooleanProperty opened = new SimpleBooleanProperty();
    private TerminalView.Listener listener;
    private ObservableBooleanValue viewActive;
    private boolean closed;

    public BrowserTerminalDockTabModel(
            BrowserAbstractSessionModel<?> browserModel,
            BrowserSessionTab origin,
            ObservableList<UUID> terminalRequests) {
        super(browserModel);
        this.origin = origin;
        this.terminalRequests = terminalRequests;
    }

    @Override
    public BaseRegionBuilder<?, ?> comp() {
        return new TerminalDockBrowserComp(dockModel, opened);
    }

    @Override
    public boolean canImmediatelyClose() {
        return true;
    }

    @Override
    public void init() throws Exception {
        listener = new TerminalView.Listener() {
            @Override
            public void onSessionOpened(TerminalView.ShellSession session) {
                if (!terminalRequests.contains(session.getRequest())) {
                    return;
                }

                opened.set(true);

                var closed = dockModel.closeOtherTerminals(session.getRequest());
                // Closing and opening windows at the same time might be problematic for some bad implementations
                if (closed) {
                    ThreadHelper.sleep(250);
                }

                if (!(session.getTerminal() instanceof TerminalView.ControllableTerminalSession t)) {
                    return;
                }
                dockModel.trackTerminal(t, true);
            }

            @Override
            public void onSessionClosed(TerminalView.ShellSession session) {
                if (!terminalRequests.contains(session.getRequest())) {
                    return;
                }

                // Ugly fix for Windows Terminal instances not closing properly if multiple windows exist
                if (AppPrefs.get().terminalType().getValue() instanceof WindowsTerminalType) {
                    var sessions = TerminalView.get().getSessions();
                    var others = sessions.stream()
                            .filter(shellSession -> shellSession.getTerminal().equals(session.getTerminal()))
                            .count();
                    if (others == 0) {
                        if (session.getTerminal() instanceof TerminalView.ControllableTerminalSession t) {
                            t.getControllable().close();
                        }
                    }
                }
            }

            @Override
            public void onTerminalClosed(TerminalView.TerminalSession instance) {
                dockModel.removeTerminal(instance);
                refreshShowingState();
            }
        };
        TerminalView.get().addListener(listener);

        // If the terminal launch fails
        ThreadHelper.runAsync(() -> {
            ThreadHelper.sleep(5000);
            if (!opened.get()) {
                refreshShowingState();
            }
        });

        viewActive = Bindings.createBooleanBinding(
                () -> {
                    return this.browserModel.getSelectedEntry().getValue() == origin
                            && AppLayoutModel.get()
                                            .getEntries()
                                            .indexOf(AppLayoutModel.get()
                                                    .getSelected()
                                                    .getValue())
                                    == 1;
                },
                this.browserModel.getSelectedEntry(),
                AppLayoutModel.get().getSelected());
        viewActive.subscribe(aBoolean -> {
            Platform.runLater(() -> {
                if (aBoolean) {
                    dockModel.activateView();
                } else {
                    dockModel.deactivateView();
                }
            });
        });
        AppDialog.getModalOverlays().addListener((ListChangeListener<? super ModalOverlay>) c -> {
            if (c.getList().size() > 0) {
                dockModel.deactivateView();
            } else {
                if (viewActive.get()) {
                    dockModel.activateView();
                } else {
                    dockModel.deactivateView();
                }
            }
        });

        GlobalTimer.scheduleUntil(Duration.ofMillis(300), false, () -> {
            if (viewActive.get()) {
                dockModel.updateCustomBounds();
            }
            return closed;
        });
    }

    @Override
    public void close() {
        if (listener != null) {
            TerminalView.get().removeListener(listener);
        }
        dockModel.onClose();
        closed = true;
    }

    @Override
    public ObservableValue<String> getName() {
        return AppI18n.observable("terminal");
    }

    @Override
    public String getIcon() {
        return null;
    }

    @Override
    public DataStoreColor getColor() {
        return null;
    }

    private void refreshShowingState() {
        var sessions = TerminalView.get().getSessions();
        var remaining = sessions.stream()
                .filter(s -> terminalRequests.contains(s.getRequest())
                        && s.getTerminal().isRunning())
                .toList();
        if (remaining.isEmpty()) {
            ((BrowserFullSessionModel) browserModel).unsplitTab(BrowserTerminalDockTabModel.this);
        }
    }
}
