package io.xpipe.app.prefs;

import io.xpipe.app.comp.BaseRegionBuilder;
import io.xpipe.app.comp.RegionBuilder;
import io.xpipe.app.comp.base.*;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.ext.PrefsChoiceValue;
import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.ext.ShellStore;
import io.xpipe.app.hub.comp.StoreChoiceComp;
import io.xpipe.app.hub.comp.StoreViewState;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.platform.*;
import io.xpipe.app.process.ShellScript;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.terminal.*;
import io.xpipe.app.util.*;
import io.xpipe.app.util.OsType;

import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.ListCell;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;

import org.kordamp.ikonli.javafx.FontIcon;

import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;

public class TerminalCategory extends AppPrefsCategory {

    public static OptionsBuilder terminalChoice(boolean docsLink) {
        var prefs = AppPrefs.get();
        var c = ChoiceComp.ofTranslatable(
                prefs.terminalType, PrefsChoiceValue.getSupported(ExternalTerminalType.class), false);
        c.maxWidth(1000);
        c.apply(struc -> {
            struc.setCellFactory(param -> {
                return new ListCell<>() {

                    {
                        // Update recommended state on changes
                        prefs.terminalMultiplexer().addListener((observable, oldValue, newValue) -> {
                            updateItem(getItem(), isEmpty());
                        });
                    }

                    @Override
                    protected void updateItem(ExternalTerminalType item, boolean empty) {
                        super.updateItem(item, empty);
                        if (empty) {
                            return;
                        }

                        setText(item.toTranslatedString().getValue());
                        if (item != ExternalTerminalType.CUSTOM) {
                            var graphic = new FontIcon(
                                    item.isRecommended() ? "mdi2c-check-decagram" : "mdi2a-alert-circle-check");
                            graphic.getStyleClass().add("graphic");
                            graphic.getStyleClass().add(item.isRecommended() ? "supported" : "unsupported");
                            setGraphic(graphic);
                        } else {
                            setGraphic(new FontIcon("mdi2m-minus-circle"));
                        }
                    }
                };
            });
        });

        var visit = new ButtonComp(AppI18n.observable("website"), new FontIcon("mdi2w-web"), () -> {
                    var t = prefs.terminalType().getValue();
                    if (t == null || t.getWebsite() == null) {
                        return;
                    }

                    Hyperlinks.open(t.getWebsite());
                })
                .minWidth(Region.USE_PREF_SIZE);

        var visitVisible = Bindings.createBooleanBinding(
                () -> {
                    var t = prefs.terminalType().getValue();
                    if (t == null || t.getWebsite() == null) {
                        return false;
                    }

                    return true;
                },
                prefs.terminalType());
        visit.visible(visitVisible);

        var h = new HorizontalComp(List.of(c.hgrow(), visit)).apply(struc -> {
            struc.setAlignment(Pos.CENTER_LEFT);
            struc.setSpacing(10);
        });
        h.maxWidth(600);

        var terminalTest = new ButtonComp(AppI18n.observable("test"), new FontIcon("mdi2p-play"), () -> {
                    ThreadHelper.runFailableAsync(() -> {
                        var term = AppPrefs.get().terminalType().getValue();
                        if (term != null) {
                            // Don't use tabs to not use multiplexer stuff
                            TerminalLaunch.builder()
                                    .title("Test")
                                    .localScript(new ShellScript(ProcessControlProvider.get()
                                            .getEffectiveLocalDialect()
                                            .getEchoCommand(
                                                    "If you can read this, the terminal integration works", false)))
                                    .preferTabs(false)
                                    .logIfEnabled(false)
                                    .pauseOnExit(true)
                                    .launch();
                        }
                    });
                })
                .padding(new Insets(6, 11, 6, 5))
                .apply(struc -> struc.setAlignment(Pos.CENTER_LEFT));

        var builder = new OptionsBuilder().pref(prefs.terminalType);
        if (!docsLink) {
            builder.documentationLink((DocumentationLink) null);
        }
        builder.addComp(h, prefs.terminalType);
        builder.pref(prefs.customTerminalCommand)
                .addComp(new TextFieldComp(prefs.customTerminalCommand, true)
                        .apply(struc -> struc.setPromptText("myterminal -e $CMD"))
                        .hide(prefs.terminalType.isNotEqualTo(ExternalTerminalType.CUSTOM)))
                .addComp(terminalTest);
        return builder;
    }

    @Override
    protected String getId() {
        return "terminal";
    }

    @Override
    protected LabelGraphic getIcon() {
        return new LabelGraphic.IconGraphic("mdi2c-console");
    }

    @Override
    protected BaseRegionBuilder<?, ?> create() {
        var prefs = AppPrefs.get();

        var tabsSettingSupported = Bindings.createBooleanBinding(
                () -> {
                    return prefs.terminalType().getValue() != null
                            && prefs.terminalType().getValue().getOpenFormat()
                                    == TerminalOpenFormat.NEW_WINDOW_OR_TABBED;
                },
                prefs.terminalType());
        var splitViewSupported = Bindings.isNotNull(TerminalSplitStrategy.getEffectiveSplitStrategyObservable());

        return new OptionsBuilder()
                .title("terminalConfiguration")
                .sub(terminalChoice(true))
                .sub(terminalProxy())
                .sub(terminalMultiplexerChoice())
                .sub(terminalPrompt())
                // .sub(terminalInitScript())
                .title("sessionLogging")
                .sub(new OptionsBuilder()
                        .pref(prefs.enableTerminalLogging)
                        .addToggle(prefs.enableTerminalLogging)
                        .nameAndDescription("terminalLoggingDirectory")
                        .documentationLink(DocumentationLink.TERMINAL_LOGGING_FILES)
                        .addComp(new ButtonComp(AppI18n.observable("openSessionLogs"), () -> {
                                    var dir = AppProperties.get().getDataDir().resolve("sessions");
                                    try {
                                        Files.createDirectories(dir);
                                        DesktopHelper.browseFile(dir);
                                    } catch (IOException e) {
                                        ErrorEventFactory.fromThrowable(e).handle();
                                    }
                                })
                                .disable(prefs.enableTerminalLogging.not())))
                .title("terminalBehaviour")
                .sub(new OptionsBuilder()
                        .pref(prefs.enableConnectionHubTerminalDocking)
                        .addToggle(prefs.enableConnectionHubTerminalDocking)
                        .hide(OsType.ofLocal() != OsType.WINDOWS)
                        .pref(prefs.enableFileBrowserTerminalDocking)
                        .addToggle(prefs.enableFileBrowserTerminalDocking)
                        .hide(OsType.ofLocal() != OsType.WINDOWS)
                        .name("terminalSplitStrategy")
                        .description(Bindings.createStringBinding(
                                () -> {
                                    return AppI18n.get(
                                            splitViewSupported.get()
                                                    ? "terminalSplitStrategyDescription"
                                                    : "terminalSplitStrategyDisabledDescription");
                                },
                                splitViewSupported,
                                AppI18n.activeLanguage()))
                        .documentationLink(DocumentationLink.TERMINAL_SPLIT)
                        .addComp(
                                ChoiceComp.ofTranslatable(
                                                prefs.terminalSplitStrategy,
                                                Arrays.asList(TerminalSplitStrategy.values()),
                                                false)
                                        .maxWidth(getCompWidth()),
                                prefs.terminalSplitStrategy)
                        .disable(splitViewSupported.not())
                        .pref(prefs.terminalAlwaysPauseOnExit)
                        .addToggle(prefs.terminalAlwaysPauseOnExit)
                        .pref(prefs.clearTerminalOnInit)
                        .addToggle(prefs.clearTerminalOnInit)
                        .pref(prefs.preferTerminalTabs)
                        .addToggle(prefs.preferTerminalTabs)
                        .hide(tabsSettingSupported.not())
                        .pref(prefs.enableTerminalStartupBell)
                        .addToggle(prefs.enableTerminalStartupBell)
                        .hide(OsType.ofLocal() == OsType.WINDOWS))
                .buildComp();
    }

    private OptionsBuilder terminalProxy() {
        var prefs = AppPrefs.get();
        var ref = new SimpleObjectProperty<DataStoreEntryRef<ShellStore>>(
                prefs.terminalProxy().getValue() != null
                        ? DataStorage.get()
                                .getStoreEntryIfPresent(prefs.terminalProxy().getValue())
                                .orElse(DataStorage.get().local())
                                .ref()
                        : DataStorage.get().local().ref());
        ref.addListener((observable, oldValue, newValue) -> {
            prefs.terminalProxy.setValue(
                    newValue != null && !newValue.get().equals(DataStorage.get().local())
                            ? newValue.get().getUuid()
                            : null);
        });
        var proxyChoice = new DelayedInitComp(
                RegionBuilder.of(() -> {
                    var choice = new StoreChoiceComp<>(
                            null,
                            ref,
                            ShellStore.class,
                            r -> r.get().equals(DataStorage.get().local()) || TerminalProxyManager.canUseAsProxy(r),
                            StoreViewState.get().getAllConnectionsCategory(),
                            null);
                    choice.hgrow();

                    var refresh = new ButtonComp(null, new LabelGraphic.IconGraphic("mdi2r-refresh"), null);
                    refresh.describe(d -> d.nameKey("refresh"));
                    refresh.apply(button -> {
                        var disable = new SimpleBooleanProperty();
                        button.disableProperty().bind(PlatformThread.sync(disable));
                        button.setOnAction(event -> {
                            ThreadHelper.runFailableAsync(() -> {
                                BooleanScope.executeExclusive(disable, () -> {
                                    ProcessControlProvider.get().refreshWsl();
                                });
                            });
                            event.consume();
                        });
                    });
                    refresh.hide(new ReadOnlyBooleanWrapper(OsType.ofLocal() != OsType.WINDOWS));
                    refresh.maxHeight(200);

                    var box = new HorizontalComp(List.of(choice, refresh));
                    box.spacing(12);
                    return box.build();
                }),
                () -> StoreViewState.get() != null && StoreViewState.get().isInitialized());
        proxyChoice.maxWidth(getCompWidth());
        return new OptionsBuilder()
                .nameAndDescription("terminalEnvironment")
                .documentationLink(DocumentationLink.TERMINAL_ENVIRONMENT)
                .addComp(proxyChoice, ref)
                .hide(OsType.ofLocal() != OsType.WINDOWS);
    }

    @SuppressWarnings("unused")
    private OptionsBuilder terminalInitScript() {
        var prefs = AppPrefs.get();
        var ref = new SimpleObjectProperty<DataStoreEntryRef<ShellStore>>();
        prefs.terminalProxy().subscribe(uuid -> {
            ref.set(
                    uuid != null
                            ? DataStorage.get()
                                    .getStoreEntryIfPresent(uuid)
                                    .orElse(DataStorage.get().local())
                                    .ref()
                            : DataStorage.get().local().ref());
        });
        return new OptionsBuilder()
                .nameAndDescription("terminalInitScript")
                .addComp(
                        IntegratedTextAreaComp.script(ref, prefs.terminalInitScript, true)
                                .maxWidth(getCompWidth()),
                        prefs.terminalInitScript);
    }

    public static OptionsBuilder terminalMultiplexerChoice() {
        var prefs = AppPrefs.get();
        var choiceBuilder = OptionsChoiceBuilder.builder()
                .property(prefs.terminalMultiplexer)
                .allowNull(true)
                .available(TerminalMultiplexer.getClasses())
                .transformer(entryComboBox -> {
                    var websiteLinkButton =
                            new ButtonComp(AppI18n.observable("website"), new FontIcon("mdi2w-web"), () -> {
                                var l = prefs.terminalMultiplexer().getValue().getDocsLink();
                                if (l != null) {
                                    Hyperlinks.open(l);
                                }
                            });
                    websiteLinkButton.minWidth(Region.USE_PREF_SIZE);
                    websiteLinkButton.disable(Bindings.createBooleanBinding(
                            () -> {
                                return prefs.terminalMultiplexer.getValue() == null
                                        || prefs.terminalMultiplexer.getValue().getDocsLink() == null;
                            },
                            prefs.terminalMultiplexer));

                    var hbox = new HBox(entryComboBox, websiteLinkButton.build());
                    HBox.setHgrow(entryComboBox, Priority.ALWAYS);
                    hbox.setSpacing(10);
                    return hbox;
                })
                .build();
        var choice = choiceBuilder.build().buildComp();
        choice.maxWidth(600);

        var testDisabled = new SimpleBooleanProperty();
        var test = new ButtonComp(AppI18n.observable("test"), new FontIcon("mdi2p-play"), () -> {
                    testDisabled.set(true);
                    ThreadHelper.runFailableAsync(() -> {
                        try {
                            var term = AppPrefs.get().terminalType().getValue();
                            if (term != null) {
                                TerminalLaunch.builder()
                                        .title("Tab 1 test")
                                        .localScript(new ShellScript(ProcessControlProvider.get()
                                                .getEffectiveLocalDialect()
                                                .getEchoCommand(
                                                        "If you can read this, the terminal multiplexer integration works",
                                                        false)))
                                        .preferTabs(true)
                                        .logIfEnabled(false)
                                        .pauseOnExit(true)
                                        .launch();

                                TerminalLaunch.builder()
                                        .title("Tab 2 test")
                                        .localScript(new ShellScript(ProcessControlProvider.get()
                                                .getEffectiveLocalDialect()
                                                .getEchoCommand(
                                                        "If you can read this, the tabbed terminal multiplexer integration works",
                                                        false)))
                                        .preferTabs(true)
                                        .logIfEnabled(false)
                                        .pauseOnExit(true)
                                        .launch();
                            }
                        } finally {
                            testDisabled.set(false);
                        }
                    });
                })
                .disable(testDisabled)
                .padding(new Insets(6, 11, 6, 5))
                .apply(struc -> struc.setAlignment(Pos.CENTER_LEFT));

        var options = new OptionsBuilder()
                .name("terminalMultiplexer")
                .description(
                        OsType.ofLocal() == OsType.WINDOWS
                                ? "terminalMultiplexerWindowsDescription"
                                : "terminalMultiplexerDescription")
                .documentationLink(DocumentationLink.TERMINAL_MULTIPLEXER)
                .addComp(choice);
        if (OsType.ofLocal() == OsType.WINDOWS) {
            options.disable(BindingsHelper.map(prefs.terminalProxy(), uuid -> uuid == null));
        }
        options.addComp(test).hide(prefs.terminalMultiplexer.isNull());
        if (OsType.ofLocal() == OsType.WINDOWS) {
            options.disable(BindingsHelper.map(prefs.terminalProxy(), uuid -> uuid == null));
        }
        return options;
    }

    private OptionsBuilder terminalPrompt() {
        var prefs = AppPrefs.get();
        var choiceBuilder = OptionsChoiceBuilder.builder()
                .property(prefs.terminalPrompt)
                .allowNull(true)
                .available(TerminalPrompt.getClasses())
                .transformer(entryComboBox -> {
                    var websiteLinkButton =
                            new ButtonComp(AppI18n.observable("website"), new FontIcon("mdi2w-web"), () -> {
                                var l = prefs.terminalPrompt().getValue().getDocsLink();
                                if (l != null) {
                                    Hyperlinks.open(l);
                                }
                            });
                    websiteLinkButton.minWidth(Region.USE_PREF_SIZE);
                    websiteLinkButton.disable(Bindings.createBooleanBinding(
                            () -> {
                                return prefs.terminalPrompt.getValue() == null
                                        || prefs.terminalPrompt.getValue().getDocsLink() == null;
                            },
                            prefs.terminalPrompt));

                    var hbox = new HBox(entryComboBox, websiteLinkButton.build());
                    HBox.setHgrow(entryComboBox, Priority.ALWAYS);
                    hbox.setSpacing(10);
                    return hbox;
                })
                .build();
        var choice = choiceBuilder.build().buildComp();
        choice.maxWidth(getCompWidth());
        return new OptionsBuilder()
                .nameAndDescription("terminalPrompt")
                .documentationLink(DocumentationLink.TERMINAL_PROMPT)
                .addComp(choice, prefs.terminalPrompt);
    }
}
