package p.studio.workspaces.assets.list;

import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import p.packer.dtos.PackerAssetSummaryDTO;
import p.packer.messages.AssetReference;
import p.packer.messages.ListAssetsRequest;
import p.studio.Container;
import p.studio.lsp.events.StudioWorkspaceEventBus;
import p.studio.projects.ProjectReference;
import p.studio.utilities.i18n.I18n;
import p.studio.workspaces.assets.AssetWorkspaceState;
import p.studio.workspaces.assets.details.AssetListPackerMappings;
import p.studio.workspaces.assets.messages.*;
import p.studio.workspaces.assets.messages.events.*;
import p.studio.workspaces.framework.StudioEventAware;
import p.studio.workspaces.framework.StudioEventBindings;

import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

public final class AssetListControl extends VBox implements StudioEventAware {
    private final ProjectReference projectReference;
    private final StudioWorkspaceEventBus workspaceBus;
    private final StudioEventBindings eventBindings = new StudioEventBindings();
    private final VBox navigatorContent = new VBox(8);
    private final TextField searchField = new TextField();
    private final ToggleButton unregisteredFilterButton = new ToggleButton();
    private final ToggleButton diagnosticsFilterButton = new ToggleButton();
    private final ToggleButton preloadFilterButton = new ToggleButton();
    private final AtomicLong refreshGeneration = new AtomicLong();

    private AssetListViewState viewState = new AssetListViewState(
            AssetWorkspaceState.loading(null),
            null,
            Container.i18n().text(I18n.ASSETS_STATE_LOADING));

    public AssetListControl(
            ProjectReference projectReference,
            StudioWorkspaceEventBus workspaceBus) {
        this.projectReference = Objects.requireNonNull(projectReference, "projectReference");
        this.workspaceBus = Objects.requireNonNull(workspaceBus, "workspaceBus");

        getStyleClass().add("assets-workspace-pane");
        setSpacing(8);
        navigatorContent.getStyleClass().add("assets-workspace-navigator-content");
        getChildren().addAll(
                createNavigatorTitle(),
                createSearchField(),
                createFilterBar(),
                createNavigatorScroll());
        installProjectionInputs();
        render();
    }

    @Override
    public StudioEventBindings eventBindings() {
        return eventBindings;
    }

    @Override
    public void registerEventSubscriptions() {
        eventBindings.listen(workspaceBus, StudioAssetsRefreshRequestedEvent.class).handle(event -> {
            requestRefresh(event.preferredAssetReference(), event.deepSync());
        });
        eventBindings.listen(workspaceBus, StudioAssetsNavigatorViewStateChangedEvent.class).handle(event -> {
            viewState = event.viewState();
            render();
        });
    }

    private Label createNavigatorTitle() {
        final var navigatorTitle = new Label();
        navigatorTitle.textProperty().bind(Container.i18n().bind(I18n.ASSETS_NAVIGATOR_TITLE));
        navigatorTitle.getStyleClass().add("assets-workspace-pane-title");
        return navigatorTitle;
    }

    private TextField createSearchField() {
        searchField.setPromptText(Container.i18n().text(I18n.ASSETS_SEARCH_PROMPT));
        searchField.getStyleClass().add("assets-workspace-search");
        return searchField;
    }

    private FlowPane createFilterBar() {
        final var filterBar = new FlowPane();
        filterBar.setHgap(6);
        filterBar.setVgap(6);
        filterBar.setPadding(new Insets(4, 0, 4, 0));
        filterBar.getStyleClass().add("assets-workspace-filter-bar");
        filterBar.getChildren().addAll(
                configureFilterButton(unregisteredFilterButton, I18n.ASSETS_FILTER_UNREGISTERED),
                configureFilterButton(diagnosticsFilterButton, I18n.ASSETS_FILTER_DIAGNOSTICS),
                configureFilterButton(preloadFilterButton, I18n.ASSETS_FILTER_PRELOAD));
        return filterBar;
    }

    private ToggleButton configureFilterButton(ToggleButton button, I18n i18n) {
        button.textProperty().bind(Container.i18n().bind(i18n));
        button.getStyleClass().addAll("studio-button", "studio-button-secondary", "studio-button-pill", "studio-button-toggle");
        return button;
    }

    private ScrollPane createNavigatorScroll() {
        final var navigatorScroll = new ScrollPane(navigatorContent);
        navigatorScroll.setFitToWidth(true);
        navigatorScroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
        navigatorScroll.getStyleClass().add("assets-workspace-navigator-scroll");
        VBox.setVgrow(navigatorScroll, Priority.ALWAYS);
        return navigatorScroll;
    }

    private void installProjectionInputs() {
        searchField.textProperty().addListener((ignored, oldValue, newValue) -> rebuildProjection());
        unregisteredFilterButton.setOnAction(ignored -> rebuildProjection());
        diagnosticsFilterButton.setOnAction(ignored -> rebuildProjection());
        preloadFilterButton.setOnAction(ignored -> rebuildProjection());
    }

    private void requestRefresh(AssetReference preferredAssetReference, boolean deepSync) {
        if (!Platform.isFxApplicationThread()) {
            Platform.runLater(() -> requestRefresh(preferredAssetReference, deepSync));
            return;
        }

        final AssetWorkspaceState previousState = viewState.workspaceState();
        final AssetReference assetReference = preferredAssetReference != null
                ? preferredAssetReference
                : previousState.selectedAssetReference();
        final long generation = refreshGeneration.incrementAndGet();

        publishNavigatorViewState(new AssetListViewState(
                AssetWorkspaceState.loading(previousState),
                null,
                Container.i18n().text(I18n.ASSETS_STATE_LOADING)));
        workspaceBus.publish(new StudioAssetsWorkspaceRefreshStartedEvent());

        Container.backgroundTasks().submit(() -> loadAssets(generation, assetReference, deepSync));
    }

    private void loadAssets(
            long generation,
            AssetReference preferredAssetReference,
            boolean deepSync) {
        try {
            final var projectContext = projectReference.toPackerProjectContext();
            final var response = Container.packer()
                    .workspaceService()
                    .listAssets(new ListAssetsRequest(projectContext, deepSync));
            final List<AssetWorkspaceAssetSummary> assets = response.assets().stream()
                    .map(this::mapAsset)
                    .toList();
            final AssetWorkspaceState workspaceState = AssetWorkspaceState.ready(assets, preferredAssetReference);
            Platform.runLater(() -> applyRefreshResult(generation, workspaceState));
        } catch (RuntimeException exception) {
            Platform.runLater(() -> applyRefreshFailure(generation, exception));
        }
    }

    private void applyRefreshResult(long generation, AssetWorkspaceState workspaceState) {
        if (generation != refreshGeneration.get()) {
            return;
        }
        final AssetListProjection projection = AssetListProjectionBuilder.build(
                workspaceState.assets(),
                projectReference.rootPath(),
                searchField.getText(),
                currentFilters());
        final AssetListViewState nextViewState = new AssetListViewState(
                workspaceState,
                projection,
                buildViewMessage(workspaceState, projection));
        publishNavigatorViewState(nextViewState);
        workspaceBus.publish(new StudioAssetsWorkspaceRefreshedEvent(
                nextViewState.workspaceState().assets().size()));
    }

    private void applyRefreshFailure(long generation, RuntimeException exception) {
        if (generation != refreshGeneration.get()) {
            return;
        }
        final String message = exception.getMessage() == null || exception.getMessage().isBlank()
                ? Container.i18n().text(I18n.ASSETS_STATE_ERROR)
                : exception.getMessage();
        publishNavigatorViewState(new AssetListViewState(
                AssetWorkspaceState.error(viewState.workspaceState(), message),
                viewState.projection(),
                message));
        workspaceBus.publish(new StudioAssetsWorkspaceRefreshFailedEvent(message));
    }

    private void rebuildProjection() {
        if (!Platform.isFxApplicationThread()) {
            Platform.runLater(this::rebuildProjection);
            return;
        }
        final AssetWorkspaceState workspaceState = viewState.workspaceState();
        if (workspaceState.status() == AssetWorkspaceStatus.LOADING || workspaceState.status() == AssetWorkspaceStatus.ERROR) {
            return;
        }

        final AssetListProjection projection = AssetListProjectionBuilder.build(
                workspaceState.assets(),
                projectReference.rootPath(),
                searchField.getText(),
                currentFilters());
        publishNavigatorViewState(new AssetListViewState(
                workspaceState,
                projection,
                buildViewMessage(workspaceState, projection)));
    }

    private void publishNavigatorViewState(AssetListViewState nextViewState) {
        viewState = Objects.requireNonNull(nextViewState, "nextViewState");
        workspaceBus.publish(new StudioAssetsNavigatorViewStateChangedEvent(nextViewState));
    }

    private AssetWorkspaceAssetSummary mapAsset(PackerAssetSummaryDTO summary) {
        return AssetListPackerMappings.mapSummary(summary);
    }

    private Set<AssetListFilter> currentFilters() {
        final EnumSet<AssetListFilter> filters = EnumSet.noneOf(AssetListFilter.class);
        if (unregisteredFilterButton.isSelected()) {
            filters.add(AssetListFilter.UNREGISTERED);
        }
        if (diagnosticsFilterButton.isSelected()) {
            filters.add(AssetListFilter.DIAGNOSTICS);
        }
        if (preloadFilterButton.isSelected()) {
            filters.add(AssetListFilter.PRELOAD);
        }
        return filters;
    }

    private String buildViewMessage(AssetWorkspaceState workspaceState, AssetListProjection projection) {
        return switch (workspaceState.status()) {
            case LOADING -> Container.i18n().text(I18n.ASSETS_STATE_LOADING);
            case EMPTY -> Container.i18n().text(I18n.ASSETS_STATE_EMPTY);
            case ERROR -> workspaceState.errorMessage();
            case READY -> projection == null || projection.isEmpty()
                    ? Container.i18n().text(I18n.ASSETS_STATE_NO_RESULTS)
                    : Container.i18n().text(I18n.ASSETS_STATE_READY);
        };
    }

    private void render() {
        navigatorContent.getChildren().clear();
        final AssetWorkspaceStatus status = viewState.workspaceState().status();
        if (status == AssetWorkspaceStatus.LOADING) {
            navigatorContent.getChildren().setAll(createMessageNode(viewState.message()));
            return;
        }
        if (status == AssetWorkspaceStatus.ERROR) {
            navigatorContent.getChildren().setAll(createMessageNode(viewState.message()));
            return;
        }
        if (status == AssetWorkspaceStatus.EMPTY || !viewState.hasProjection()) {
            navigatorContent.getChildren().setAll(createMessageNode(viewState.message()));
            return;
        }

        for (AssetListGroup group : viewState.projection().groups()) {
            final var groupBox = createGroupBox(group.label());
            for (AssetWorkspaceAssetSummary asset : group.assets()) {
                groupBox.getChildren().add(new AssetListItemControl(
                        projectReference,
                        workspaceBus,
                        asset));
            }
            navigatorContent.getChildren().add(groupBox);
        }
    }

    private VBox createGroupBox(String label) {
        final var groupBox = new VBox(6);
        groupBox.getStyleClass().add("assets-workspace-group");
        groupBox.getChildren().add(createGroupLabel(label));
        return groupBox;
    }

    private Label createGroupLabel(String text) {
        final var groupLabel = new Label(text);
        groupLabel.getStyleClass().add("assets-workspace-group-label");
        return groupLabel;
    }

    private Node createMessageNode(String text) {
        final var label = new Label(text);
        label.getStyleClass().add("assets-workspace-empty-state");
        label.setWrapText(true);
        return label;
    }
}
