package io.xpipe.app.core.window;

import io.xpipe.app.core.*;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.GlobalTimer;
import io.xpipe.app.util.OsType;

import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.input.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

import java.time.Duration;
import java.time.Instant;
import java.util.List;

public class AppWindowStyle {

    public static void setSceneFill(Scene scene) {
        if (OsType.ofLocal() != OsType.LINUX || AppPrefs.get() == null) {
            scene.setFill(Color.TRANSPARENT);
            return;
        }

        scene.fillProperty()
                .bind(Bindings.createObjectBinding(
                        () -> {
                            return AppPrefs.get().theme().getValue().isDark()
                                    ? Color.BLACK
                                    : Color.WHITE;
                        },
                        AppPrefs.get().theme()));
    }

    public static void addMaximizedPseudoClass(Stage stage) {
        stage.getScene().rootProperty().subscribe(root -> {
            stage.maximizedProperty().subscribe(v -> {
                root.pseudoClassStateChanged(PseudoClass.getPseudoClass("maximized"), v);
            });
        });
    }

    public static void addFontSize(Scene scene) {
        scene.rootProperty().subscribe(root -> {
            AppFontSizes.base(root);
        });
    }

    public static void addNavigationPseudoClasses(Scene scene) {
        var keyInput = new SimpleBooleanProperty();
        var changed = new SimpleBooleanProperty();
        keyInput.addListener((observable, oldValue, newValue) -> {
            changed.set(true);
        });

        GlobalTimer.scheduleUntil(Duration.ofSeconds(1), false, () -> {
            if (changed.get()) {
                Platform.runLater(() -> {
                    var r = scene.getRoot();
                    var kb = keyInput.get();
                    if (r != null) {
                        // This property is broken on some systems
                        var acc = Platform.isAccessibilityActive();
                        r.pseudoClassStateChanged(PseudoClass.getPseudoClass("key-navigation"), kb);
                        r.pseudoClassStateChanged(PseudoClass.getPseudoClass("normal-navigation"), !kb);
                        r.pseudoClassStateChanged(PseudoClass.getPseudoClass("accessibility-navigation"), acc);
                    }
                    changed.set(false);
                });
            }

            return false;
        });

        scene.focusOwnerProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue == null || !newValue.isFocusTraversable()) {
                keyInput.set(false);
            }
        });

        scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
            var c = event.getCode();
            var list = List.of(KeyCode.SPACE, KeyCode.ENTER, KeyCode.SHIFT, KeyCode.TAB);
            keyInput.set(list.stream().anyMatch(keyCode -> keyCode == c)
                    || event.getCode().isNavigationKey());
        });
        scene.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
            keyInput.set(false);
        });
    }

    public static void addIcons(Stage stage) {
        stage.getIcons().clear();

        // This allows for assigning logos even if AppImages has not been initialized yet
        var dir = OsType.ofLocal() == OsType.MACOS ? "logo/padded" : "logo/full";
        AppResources.with(AppResources.MAIN_MODULE, dir, path -> {
            var size =
                    switch (OsType.ofLocal()) {
                        case OsType.Linux ignored -> 128;
                        case OsType.MacOs ignored -> 128;
                        case OsType.Windows ignored -> 32;
                    };
            stage.getIcons().add(AppImages.loadImage(path.resolve("logo_" + size + "x" + size + ".png")));
        });
    }

    public static void addStylesheets(Scene scene) {
        AppStyle.addStylesheets(scene);

        scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
            if (AppProperties.get().isDevelopmentEnvironment()
                    && event.getCode().equals(KeyCode.F3)) {
                AppStyle.reloadStylesheets(scene);
                TrackEvent.debug("Reloaded stylesheets");
                event.consume();
            }
        });
        TrackEvent.debug("Set stylesheet reload listener");
    }

    public static void addClickShield(Stage stage) {
        if (OsType.ofLocal() != OsType.MACOS) {
            return;
        }

        var focusInstant = new SimpleObjectProperty<>(Instant.EPOCH);
        stage.focusedProperty().subscribe((newValue) -> {
            if (newValue) {
                focusInstant.set(Instant.now());
            }
        });
        var blockNextPress = new SimpleBooleanProperty();
        stage.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
            var elapsed = Duration.between(focusInstant.get(), Instant.now());
            if (elapsed.toMillis() < 50) {
                blockNextPress.set(true);
                event.consume();
            } else {
                blockNextPress.set(false);
            }
        });
        stage.addEventFilter(MouseEvent.MOUSE_RELEASED, event -> {
            var elapsed = Duration.between(focusInstant.get(), Instant.now());
            if (elapsed.toMillis() < 100 && blockNextPress.get()) {
                event.consume();
            }
        });
        stage.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
            var elapsed = Duration.between(focusInstant.get(), Instant.now());
            if (elapsed.toMillis() < 1000 && blockNextPress.get()) {
                blockNextPress.set(false);
                event.consume();
            }
        });
        stage.addEventFilter(MouseEvent.ANY, event -> {
            if (!stage.isFocused()) {
                event.consume();
            }
        });
    }
}
