package p.studio.controls.shell;

import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.VBox;
import p.studio.Container;
import p.studio.controls.lifecycle.StudioControlLifecycle;
import p.studio.controls.lifecycle.StudioControlLifecycleSupport;
import p.studio.lsp.events.*;
import p.studio.projects.ProjectReference;
import p.studio.utilities.events.EventSubscription;
import p.studio.utilities.i18n.I18n;
import p.studio.workspaces.assets.messages.events.StudioAssetsWorkspaceRefreshFailedEvent;
import p.studio.workspaces.assets.messages.events.StudioAssetsWorkspaceRefreshStartedEvent;
import p.studio.workspaces.assets.messages.events.StudioAssetsWorkspaceRefreshedEvent;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public final class StudioActivityFeedControl extends VBox implements StudioControlLifecycle {
    private final ProjectReference projectReference;
    private final StudioActivityStorageService storageService;
    private final ObservableList<StudioActivityEntry> entries = FXCollections.observableArrayList();
    private final List<EventSubscription> subscriptions = new ArrayList<>();
    private final Label progressLabel = new Label();
    private final ProgressBar progressBar = new ProgressBar();
    private final ListView<StudioActivityEntry> listView = new ListView<>(entries);

    public StudioActivityFeedControl(ProjectReference projectReference) {
        this(projectReference, new StudioActivityStorageService());
    }

    StudioActivityFeedControl(ProjectReference projectReference, StudioActivityStorageService storageService) {
        StudioControlLifecycleSupport.install(this, this);
        this.projectReference = Objects.requireNonNull(projectReference, "projectReference");
        this.storageService = Objects.requireNonNull(storageService, "storageService");
        getStyleClass().add("studio-activity-feed");
        setSpacing(10);
        setPadding(new Insets(12));

        progressLabel.setText(Container.i18n().text(I18n.ACTIVITY_PROGRESS_IDLE));
        progressLabel.getStyleClass().add("studio-activity-progress-label");
        progressBar.getStyleClass().add("studio-activity-progress-bar");
        progressBar.setVisible(false);
        progressBar.setManaged(false);

        listView.getStyleClass().add("studio-activity-list");
        listView.setCellFactory(ignored -> new ActivityCell());

        entries.setAll(this.storageService.load(projectReference));
        getChildren().addAll(progressLabel, progressBar, listView);
    }

    @Override
    public void subscribe() {
        if (!subscriptions.isEmpty()) {
            return;
        }
        final StudioEventBus eventBus = Container.eventBus();
        subscriptions.add(eventBus.subscribe(StudioProjectOpenedEvent.class, this::onEvent));
        subscriptions.add(eventBus.subscribe(StudioProjectLoadingStartedEvent.class, this::onEvent));
        subscriptions.add(eventBus.subscribe(StudioProjectLoadingProgressEvent.class, this::onProjectLoadingProgress));
        subscriptions.add(eventBus.subscribe(StudioProjectLoadingCompletedEvent.class, this::onProjectLoadingCompleted));
        subscriptions.add(eventBus.subscribe(StudioProjectLoadingFailedEvent.class, this::onProjectLoadingFailed));
        subscriptions.add(eventBus.subscribe(StudioAssetsWorkspaceRefreshStartedEvent.class, this::onAssetsRefreshStarted));
        subscriptions.add(eventBus.subscribe(StudioAssetsWorkspaceRefreshedEvent.class, this::onAssetsRefreshFinished));
        subscriptions.add(eventBus.subscribe(StudioAssetsWorkspaceRefreshFailedEvent.class, this::onAssetsRefreshFailed));
        subscriptions.add(eventBus.subscribe(StudioPackerOperationEvent.class, this::onPackerOperation));
        subscriptions.add(eventBus.subscribe(StudioExecutionLifecycleEvent.class, this::onEvent));
    }

    @Override
    public void unsubscribe() {
        subscriptions.forEach(EventSubscription::unsubscribe);
        subscriptions.clear();
    }

    private void onEvent(StudioEvent event) {
        StudioActivityEventMapper.map(event).ifPresent(this::appendEntry);
    }

    private void onProjectLoadingProgress(StudioProjectLoadingProgressEvent event) {
        Platform.runLater(() -> {
            progressLabel.setText(event.message());
            progressBar.setVisible(true);
            progressBar.setManaged(true);
            if (event.indeterminate()) {
                progressBar.setProgress(ProgressBar.INDETERMINATE_PROGRESS);
            } else {
                progressBar.setProgress(event.progress());
            }
        });
    }

    private void onProjectLoadingCompleted(StudioProjectLoadingCompletedEvent event) {
        onEvent(event);
        clearProgress();
    }

    private void onProjectLoadingFailed(StudioProjectLoadingFailedEvent event) {
        onEvent(event);
        clearProgress();
    }

    private void onAssetsRefreshStarted(StudioAssetsWorkspaceRefreshStartedEvent event) {
        onEvent(event);
        Platform.runLater(() -> {
            progressLabel.setText(Container.i18n().text(I18n.ACTIVITY_PROGRESS_ASSETS_REFRESH));
            progressBar.setVisible(true);
            progressBar.setManaged(true);
            progressBar.setProgress(ProgressBar.INDETERMINATE_PROGRESS);
        });
    }

    private void onAssetsRefreshFinished(StudioAssetsWorkspaceRefreshedEvent event) {
        onEvent(event);
        clearProgress();
    }

    private void onAssetsRefreshFailed(StudioAssetsWorkspaceRefreshFailedEvent event) {
        onEvent(event);
        clearProgress();
    }

    private void onPackerOperation(StudioPackerOperationEvent event) {
        onEvent(event);
        if (event.progress() != null) {
            Platform.runLater(() -> {
                progressLabel.setText(event.summary());
                progressBar.setVisible(true);
                progressBar.setManaged(true);
                progressBar.setProgress(event.indeterminate() ? ProgressBar.INDETERMINATE_PROGRESS : event.progress());
            });
        }
        if (event.kind() == p.packer.events.PackerEventKind.ACTION_APPLIED
                || event.kind() == p.packer.events.PackerEventKind.ACTION_FAILED) {
            clearProgress();
        }
    }

    private void clearProgress() {
        Platform.runLater(() -> {
            progressLabel.setText(Container.i18n().text(I18n.ACTIVITY_PROGRESS_IDLE));
            progressBar.setVisible(false);
            progressBar.setManaged(false);
        });
    }

    private void appendEntry(StudioActivityEntry entry) {
        Platform.runLater(() -> {
            if (!entries.isEmpty()) {
                final StudioActivityEntry latest = entries.getFirst();
                if (latest.source().equals(entry.source())
                        && latest.message().equals(entry.message())
                        && latest.severity() == entry.severity()) {
                    return;
                }
            }
            entries.addFirst(entry);
            while (entries.size() > StudioActivityStorageService.MAX_HISTORY) {
                entries.removeLast();
            }
            storageService.save(projectReference, List.copyOf(entries));
        });
    }

    private static final class ActivityCell extends ListCell<StudioActivityEntry> {
        @Override
        protected void updateItem(StudioActivityEntry item, boolean empty) {
            super.updateItem(item, empty);
            getStyleClass().removeAll(
                    "studio-activity-cell-info",
                    "studio-activity-cell-success",
                    "studio-activity-cell-warning",
                    "studio-activity-cell-error");

            if (empty || item == null) {
                setText(null);
                setGraphic(null);
                return;
            }

            final Label source = new Label(item.source());
            source.getStyleClass().add("studio-activity-cell-source");
            final Label message = new Label(item.message());
            message.getStyleClass().add("studio-activity-cell-message");
            message.setWrapText(true);
            final VBox content = new VBox(4, source, message);
            content.setAlignment(Pos.CENTER_LEFT);
            setGraphic(content);
            getStyleClass().add("studio-activity-cell-" + item.severity().name().toLowerCase());
        }
    }
}
