package io.xpipe.app.prefs;

import io.xpipe.app.comp.BaseRegionBuilder;
import io.xpipe.app.comp.RegionBuilder;
import io.xpipe.app.comp.RegionDescriptor;
import io.xpipe.app.comp.base.*;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.core.AppFontSizes;
import io.xpipe.app.core.window.AppDialog;
import io.xpipe.app.icon.SystemIconManager;
import io.xpipe.app.icon.SystemIconSource;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.platform.LabelGraphic;
import io.xpipe.app.platform.OptionsBuilder;
import io.xpipe.app.platform.PlatformThread;
import io.xpipe.app.process.OsFileSystem;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.*;
import io.xpipe.app.util.FilePath;
import io.xpipe.app.util.OsType;

import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;

import org.apache.commons.io.FilenameUtils;

import java.net.URI;
import java.nio.file.Files;
import java.util.*;

public class IconsCategory extends AppPrefsCategory {

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

    @Override
    protected LabelGraphic getIcon() {
        return new LabelGraphic.IconGraphic("mdi2v-view-grid-plus-outline");
    }

    @Override
    protected BaseRegionBuilder<?, ?> create() {
        return new OptionsBuilder()
                .title("customIcons")
                .sub(new OptionsBuilder()
                        .nameAndDescription("iconSources")
                        .documentationLink(DocumentationLink.ICONS)
                        .addComp(createOverview().maxWidth(getCompWidth()))
                        .nameAndDescription("preferMonochromeIcons")
                        .addToggle(AppPrefs.get().preferMonochromeIcons))
                .buildComp();
    }

    private BaseRegionBuilder<?, ?> createOverview() {
        var sources = FXCollections.<SystemIconSource>observableArrayList();
        AppPrefs.get().getIconSources().subscribe((newValue) -> {
            sources.setAll(SystemIconManager.getAllSources());
        });
        var box = new ListBoxViewComp<>(sources, sources, s -> createSourceEntry(s, sources), false);
        box.apply(struc -> {
            struc.minHeightProperty().bind(((Region) struc.getContent()).heightProperty());
        });

        var busy = new SimpleBooleanProperty(false);
        var refreshButton = new TileButtonComp("refreshSources", "refreshSourcesDescription", "mdi2r-refresh", e -> {
            ThreadHelper.runFailableAsync(() -> {
                try (var ignored = new BooleanScope(busy).start()) {
                    SystemIconManager.rebuild();
                }
            });
            e.consume();
        });
        refreshButton.disable(PlatformThread.sync(busy.or(Bindings.isEmpty(sources))));
        refreshButton.maxWidth(2000);

        var addGitButton =
                new TileButtonComp("addGitIconSource", "addGitIconSourceDescription", "mdi2a-access-point-plus", e -> {
                    var remote = new SimpleStringProperty();
                    var modal = ModalOverlay.of(
                            "repositoryUrl",
                            RegionBuilder.of(() -> {
                                        var creationName = new TextField();
                                        creationName.textProperty().bindBidirectional(remote);
                                        return creationName;
                                    })
                                    .prefWidth(350));
                    modal.withDefaultButtons(() -> {
                        if (remote.get() == null || remote.get().isBlank()) {
                            return;
                        }

                        // Don't use the git sync repo itself ...
                        if (remote.get()
                                .equals(AppPrefs.get().storageGitRemote().get())) {
                            return;
                        }

                        String id = null;
                        try {
                            var uri = URI.create(remote.get());
                            var path = uri.getPath();
                            if (path != null) {
                                var name = FilenameUtils.getBaseName(path);
                                if (!name.isBlank()) {
                                    // Windows has the most strict file name rules
                                    id = OsFileSystem.of(OsType.WINDOWS).makeFileSystemCompatible(name);
                                }
                            }
                        } catch (Exception ignored) {
                        }

                        if (id == null) {
                            id = UUID.randomUUID().toString();
                        }

                        var source = SystemIconSource.GitRepository.builder()
                                .remote(remote.get())
                                .id(id)
                                .build();
                        if (sources.stream()
                                .noneMatch(s -> s instanceof SystemIconSource.GitRepository g
                                        && g.getRemote().equals(remote.get()))) {
                            sources.add(source);
                            var nl = new ArrayList<>(
                                    AppPrefs.get().getIconSources().getValue());
                            nl.add(source);
                            AppPrefs.get().iconSources.setValue(nl);
                        }
                    });
                    modal.show();
                    e.consume();
                });
        addGitButton.maxWidth(2000);

        var addDirectoryButton = new TileButtonComp(
                "addDirectoryIconSource", "addDirectoryIconSourceDescription", "mdi2f-folder-plus", e -> {
                    var dir = new SimpleObjectProperty<FilePath>();
                    var modal = ModalOverlay.of(
                            "iconDirectory",
                            new ContextualFileReferenceChoiceComp(
                                            new SimpleObjectProperty<>(
                                                    DataStorage.get().local().ref()),
                                            dir,
                                            null,
                                            List.of(),
                                            en -> en.equals(DataStorage.get().local()),
                                            true)
                                    .prefWidth(350));
                    modal.withDefaultButtons(() -> {
                        if (dir.get() == null) {
                            return;
                        }

                        var path = dir.get().asLocalPathIfPossible();
                        if (path.isEmpty() || Files.isRegularFile(path.get())) {
                            throw ErrorEventFactory.expected(
                                    new IllegalArgumentException(
                                            "A custom icon source must be a directory containing .svg files, not a single file"));
                        }

                        var source = SystemIconSource.Directory.builder()
                                .path(path.get())
                                .id(UUID.randomUUID().toString())
                                .build();
                        if (sources.stream()
                                .noneMatch(s -> s instanceof SystemIconSource.Directory d
                                        && d.getPath().equals(path.get()))) {
                            sources.add(source);
                            var nl = new ArrayList<>(
                                    AppPrefs.get().getIconSources().getValue());
                            nl.add(source);
                            AppPrefs.get().iconSources.setValue(nl);
                        }
                    });
                    modal.show();
                    e.consume();
                });
        addDirectoryButton.maxWidth(2000);

        var vbox = new VerticalComp(List.of(
                RegionBuilder.vspacer(10),
                box,
                RegionBuilder.hseparator(),
                refreshButton,
                RegionBuilder.hseparator(),
                addDirectoryButton,
                addGitButton));
        vbox.spacing(10);
        return vbox;
    }

    private BaseRegionBuilder<?, ?> createSourceEntry(SystemIconSource source, List<SystemIconSource> sources) {
        var delete = new IconButtonComp(new LabelGraphic.IconGraphic("mdal-delete_outline"), () -> {
            if (!AppDialog.confirm("iconSourceDeletion")) {
                return;
            }

            var nl = new ArrayList<>(AppPrefs.get().getIconSources().getValue());
            nl.remove(source);
            AppPrefs.get().iconSources.setValue(nl);
            sources.remove(source);
        });
        if (!AppPrefs.get().getIconSources().getValue().contains(source)) {
            delete.disable(new SimpleBooleanProperty(true));
        }

        var disabled = AppCache.getNonNull("disabledIconSources", Set.class, () -> Set.<String>of());
        var enabled = RegionBuilder.of(() -> {
            var cb = new CheckBox();
            RegionDescriptor.builder().nameKey("enabled").build().apply(cb);
            cb.setSelected(!disabled.contains(source.getId()));
            cb.selectedProperty().addListener((observable, oldValue, newValue) -> {
                var set = new LinkedHashSet<>(
                        AppCache.getNonNull("disabledIconSources", Set.class, () -> Set.<String>of()));
                if (newValue) {
                    set.remove(source.getId());
                } else {
                    set.add(source.getId());
                }
                AppCache.update("disabledIconSources", set);
            });
            cb.setAlignment(Pos.BOTTOM_CENTER);
            cb.setPadding(new Insets(0, 0, 1, 0));
            AppFontSizes.sm(cb);
            cb.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
                cb.setSelected(!cb.isSelected());
                e.consume();
            });
            return cb;
        });

        var buttons = new HorizontalComp(List.of(enabled, delete));
        buttons.apply(struc -> struc.setFillHeight(true));
        buttons.spacing(15);

        var tile = new TileButtonComp(
                new SimpleStringProperty(
                        AppPrefs.get().getIconSources().getValue().contains(source)
                                ? source.getDisplayName()
                                : source.getId()),
                new SimpleStringProperty(source.getDescription()),
                new SimpleObjectProperty<>(source.getIcon()),
                actionEvent -> {
                    ThreadHelper.runFailableAsync(() -> {
                        source.open();
                    });
                });
        tile.setRight(buttons);
        tile.setIconSize(1.0);
        tile.maxWidth(2000);
        return tile;
    }
}
