package p.studio.window;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import javafx.application.Platform;
import javafx.stage.Stage;
import p.studio.projects.ProjectReference;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

public final class WindowStateService {
    private static final ObjectMapper MAPPER = new ObjectMapper()
            .enable(SerializationFeature.INDENT_OUTPUT);

    private final Path storageFile;

    public WindowStateService(Path storageFile) {
        this.storageFile = Objects.requireNonNull(storageFile, "storageFile").toAbsolutePath().normalize();
    }

    public void installLauncherState(Stage stage, double defaultWidth, double defaultHeight) {
        final WindowStateRecord launcherState = loadState().launcher();
        stage.setWidth(defaultWidth);
        stage.setHeight(defaultHeight);
        if (launcherState != null && launcherState.maximized()) {
            Platform.runLater(() -> stage.setMaximized(false));
        }

        stage.maximizedProperty().addListener((ignored, oldValue, newValue) -> {
            if (Boolean.TRUE.equals(newValue)) {
                Platform.runLater(() -> stage.setMaximized(false));
            }
        });
        stage.fullScreenProperty().addListener((ignored, oldValue, newValue) -> {
            if (Boolean.TRUE.equals(newValue)) {
                Platform.runLater(() -> stage.setFullScreen(false));
            }
        });
    }

    public void installProjectShellState(
            Stage stage,
            ProjectReference projectReference,
            double defaultWidth,
            double defaultHeight) {
        final String projectKey = normalizeProjectKey(projectReference);
        final WindowStateRecord state = loadState().projects().get(projectKey);
        applyState(stage, state, defaultWidth, defaultHeight);

        stage.xProperty().addListener((ignored, oldValue, newValue) -> saveProjectState(projectKey, stage));
        stage.yProperty().addListener((ignored, oldValue, newValue) -> saveProjectState(projectKey, stage));
        stage.widthProperty().addListener((ignored, oldValue, newValue) -> saveProjectState(projectKey, stage));
        stage.heightProperty().addListener((ignored, oldValue, newValue) -> saveProjectState(projectKey, stage));
        stage.maximizedProperty().addListener((ignored, oldValue, newValue) -> saveProjectState(projectKey, stage));
    }

    private void applyState(Stage stage, WindowStateRecord state, double defaultWidth, double defaultHeight) {
        stage.setWidth(state != null && state.width() > 0 ? state.width() : defaultWidth);
        stage.setHeight(state != null && state.height() > 0 ? state.height() : defaultHeight);
        if (state != null && !Double.isNaN(state.x())) {
            stage.setX(state.x());
        }
        if (state != null && !Double.isNaN(state.y())) {
            stage.setY(state.y());
        }
        if (state != null && state.maximized()) {
            stage.setMaximized(true);
        }
    }

    private void saveProjectState(String projectKey, Stage stage) {
        final WindowStateFile file = loadState();
        file.projects().put(projectKey, new WindowStateRecord(
                stage.getX(),
                stage.getY(),
                stage.getWidth(),
                stage.getHeight(),
                stage.isMaximized()));
        storeState(file);
    }

    private WindowStateFile loadState() {
        if (!Files.isRegularFile(storageFile)) {
            return WindowStateFile.empty();
        }

        try {
            final WindowStateFile loaded = MAPPER.readValue(storageFile.toFile(), WindowStateFile.class);
            return loaded == null ? WindowStateFile.empty() : loaded.normalize();
        } catch (IOException ioException) {
            throw new UncheckedIOException(ioException);
        }
    }

    private void storeState(WindowStateFile file) {
        try {
            Files.createDirectories(storageFile.getParent());
            MAPPER.writeValue(storageFile.toFile(), file.normalize());
        } catch (IOException ioException) {
            throw new UncheckedIOException(ioException);
        }
    }

    private String normalizeProjectKey(ProjectReference projectReference) {
        return Objects.requireNonNull(projectReference, "projectReference")
                .rootPath()
                .toAbsolutePath()
                .normalize()
                .toString();
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    private record WindowStateFile(WindowStateRecord launcher, Map<String, WindowStateRecord> projects) {
        private static WindowStateFile empty() {
            return new WindowStateFile(null, new LinkedHashMap<>());
        }

        private WindowStateFile normalize() {
            return new WindowStateFile(launcher, projects == null ? new LinkedHashMap<>() : new LinkedHashMap<>(projects));
        }
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    private record WindowStateRecord(double x, double y, double width, double height, boolean maximized) {
    }
}
