package p.studio.workspaces.assets.details.bank;

import javafx.application.Platform;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import p.packer.dtos.PackerBankCompositionDetailsDTO;
import p.packer.dtos.PackerBankCompositionFileDTO;
import p.packer.messages.ApplyBankCompositionRequest;
import p.packer.messages.GetAssetDetailsRequest;
import p.studio.Container;
import p.studio.controls.banks.StudioAssetCapacityMeter;
import p.studio.controls.forms.StudioFormEditScopeChangedEvent;
import p.studio.controls.forms.StudioFormMode;
import p.studio.controls.forms.StudioFormSection;
import p.studio.lsp.events.StudioWorkspaceEventBus;
import p.studio.projects.ProjectReference;
import p.studio.utilities.i18n.I18n;
import p.studio.workspaces.assets.messages.AssetWorkspaceDetailsViewState;
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionDetails;
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile;
import p.studio.workspaces.assets.messages.events.StudioAssetBankCompositionAppliedEvent;
import p.studio.workspaces.assets.messages.events.StudioAssetBankCompositionApplyFailedEvent;
import p.studio.workspaces.assets.messages.events.StudioAssetBankCompositionApplyRequestedEvent;
import p.studio.workspaces.assets.messages.events.StudioAssetBankCompositionCapacityChangedEvent;
import p.studio.workspaces.assets.messages.events.StudioAssetBankCompositionDraftChangedEvent;
import p.studio.workspaces.assets.messages.events.StudioBankCompositionChangedEvent;
import p.studio.workspaces.assets.messages.events.StudioAssetsDetailsViewStateChangedEvent;
import p.studio.workspaces.framework.StudioSubscriptionBag;

import java.util.Objects;

public final class AssetDetailsBankCompositionControl extends StudioFormSection {
    private static final double CAPACITY_METER_WIDTH = 180.0d;
    private static final String SECTION_ID = "asset-details.bank-composition";

    private final ProjectReference projectReference;
    private final StudioWorkspaceEventBus workspaceBus;
    private final StudioSubscriptionBag subscriptions = new StudioSubscriptionBag();
    private final AssetDetailsBankCompositionDualListView dualListView = new AssetDetailsBankCompositionDualListView();
    private final StudioAssetCapacityMeter capacityMeter = new StudioAssetCapacityMeter();
    private final AssetDetailsBankCompositionCoordinator coordinator = new AssetDetailsBankCompositionCoordinator();
    private final HBox body = new HBox(16, dualListView, capacityMeter);
    private final VBox content = new VBox(12, body);

    private AssetWorkspaceDetailsViewState viewState;

    public AssetDetailsBankCompositionControl(ProjectReference projectReference, StudioWorkspaceEventBus workspaceBus) {
        this.projectReference = Objects.requireNonNull(projectReference, "projectReference");
        this.workspaceBus = Objects.requireNonNull(workspaceBus, "workspaceBus");
        capacityMeter.setMinWidth(CAPACITY_METER_WIDTH);
        capacityMeter.setPrefWidth(CAPACITY_METER_WIDTH);
        capacityMeter.setMaxWidth(CAPACITY_METER_WIDTH);
        capacityMeter.setMinHeight(Region.USE_PREF_SIZE);
        capacityMeter.setMaxHeight(Double.MAX_VALUE);
        body.getStyleClass().add("assets-details-bank-composition-body");
        body.setFillHeight(true);
        body.setMinHeight(0.0d);
        body.setMaxHeight(Double.MAX_VALUE);
        content.setFillWidth(true);
        content.setMinHeight(0.0d);
        content.setMaxHeight(Double.MAX_VALUE);
        VBox.setVgrow(body, Priority.ALWAYS);
        HBox.setHgrow(dualListView, Priority.ALWAYS);
        dualListView.setMaxWidth(Double.MAX_VALUE);
        dualListView.setMinHeight(0.0d);
        dualListView.setMaxHeight(Double.MAX_VALUE);
    }

    @Override
    public void subscribe() {
        subscriptions.add(workspaceBus.subscribe(StudioAssetsDetailsViewStateChangedEvent.class, event -> {
            viewState = event.viewState();
            coordinator.replaceDetails(viewState == null ? null : viewState.selectedAssetDetails());
            if (viewState == null || viewState.selectedAssetReference() == null) {
                publishEditScope(workspaceBus, currentScopeKey(), null);
            }
            renderSection();
            publishStateNotifications();
        }));
        subscriptions.add(workspaceBus.subscribe(StudioFormEditScopeChangedEvent.class,
                event -> handleEditScopeChanged(event, currentScopeKey())));
        subscriptions.add(workspaceBus.subscribe(StudioBankCompositionChangedEvent.class, event -> {
            if (viewState == null || !Objects.equals(viewState.selectedAssetReference(), event.assetReference())) {
                return;
            }
            viewState = new AssetWorkspaceDetailsViewState(
                    viewState.detailsStatus(),
                    viewState.selectedAssetReference(),
                    viewState.selectedAssetDetails().withBankComposition(event.bankComposition()),
                    viewState.detailsErrorMessage());
            coordinator.replaceDetails(viewState.selectedAssetDetails());
            renderSection();
            publishStateNotifications();
        }));
    }

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

    @Override
    protected void renderSection() {
        if (viewState == null || viewState.selectedAssetDetails() == null) {
            clearRenderedSection();
            return;
        }
        if (!coordinator.ready()) {
            clearRenderedSection();
            return;
        }

        final AssetDetailsBankCompositionViewModel viewModel = coordinator.viewModel();

        dualListView.setAssetRoot(viewState.selectedAssetDetails().summary().assetRoot());
        dualListView.setLeftItems(viewModel.availableFiles());
        dualListView.setRightItems(viewModel.selectedFiles());
        dualListView.setInteractionEnabled(viewModel.editing());
        dualListView.setMoveToRightAllowed(!viewModel.capacityState().blocked());
        dualListView.setOnMoveToRight(items -> {
            coordinator.moveToSelected(items);
            rerenderPreservingScrollPosition();
            publishStateNotifications();
        });
        dualListView.setOnMoveToLeft(items -> {
            coordinator.moveToAvailable(items);
            rerenderPreservingScrollPosition();
            publishStateNotifications();
        });
        dualListView.setOnMoveUp(index -> {
            coordinator.moveUp(index);
            rerenderPreservingScrollPosition();
            publishStateNotifications();
        });
        dualListView.setOnMoveDown(index -> {
            coordinator.moveDown(index);
            rerenderPreservingScrollPosition();
            publishStateNotifications();
        });

        capacityMeter.setProgress(viewModel.capacityState().progress());
        capacityMeter.setSeverity(viewModel.capacityState().severity());
        capacityMeter.setLabelText("Bank Capacity");
        capacityMeter.setHintText("");

        renderFormSection(content);
    }

    @Override
    protected String sectionTitle() {
        return Container.i18n().text(I18n.ASSETS_SECTION_BANK_COMPOSITION);
    }

    @Override
    protected StudioFormMode formMode() {
        return coordinator.ready() && coordinator.viewModel().editing() ? StudioFormMode.EDITING : StudioFormMode.READ_ONLY;
    }

    @Override
    protected boolean isDirty() {
        return coordinator.ready() && coordinator.viewModel().dirty();
    }

    @Override
    protected String sectionStyleClass() {
        return "assets-details-bank-composition-section";
    }

    @Override
    protected String formSectionId() {
        return SECTION_ID;
    }

    @Override
    protected void beginEdit() {
        coordinator.beginEdit();
        publishEditScope(workspaceBus, currentScopeKey(), SECTION_ID);
        publishStateNotifications();
    }

    @Override
    protected void apply() {
        if (viewState == null || viewState.selectedAssetReference() == null || !coordinator.ready()) {
            return;
        }
        final var assetReference = viewState.selectedAssetReference();
        final var selectedPaths = coordinator.viewModel().selectedFiles().stream()
                .map(file -> file.path())
                .toList();
        workspaceBus.publish(new StudioAssetBankCompositionApplyRequestedEvent(assetReference));
        Container.backgroundTasks().submit(() -> {
            final var request = new ApplyBankCompositionRequest(
                    projectReference.toPackerProjectContext(),
                    assetReference,
                    selectedPaths);
            try {
                final var response = Container.packer().workspaceService().applyBankComposition(request);
                Platform.runLater(() -> {
                    if (response.success()) {
                        publishEditScope(workspaceBus, currentScopeKey(), null);
                        workspaceBus.publish(new StudioAssetBankCompositionAppliedEvent(assetReference));
                        Container.backgroundTasks().submit(() -> publishAppliedBankComposition(assetReference));
                    } else {
                        workspaceBus.publish(new StudioAssetBankCompositionApplyFailedEvent(
                                assetReference,
                                response.errorMessage()));
                    }
                });
            } catch (Exception exception) {
                Platform.runLater(() -> workspaceBus.publish(new StudioAssetBankCompositionApplyFailedEvent(
                        assetReference,
                        exception.getMessage())));
            }
        });
    }

    private void publishAppliedBankComposition(p.packer.messages.AssetReference assetReference) {
        try {
            final var detailsResult = Container.packer().workspaceService().getAssetDetails(new GetAssetDetailsRequest(
                    projectReference.toPackerProjectContext(),
                    assetReference));
            final AssetWorkspaceBankCompositionDetails bankComposition = mapBankComposition(detailsResult.details().bankComposition());
            Platform.runLater(() -> workspaceBus.publish(new StudioBankCompositionChangedEvent(assetReference, bankComposition)));
        } catch (Exception exception) {
            Platform.runLater(() -> workspaceBus.publish(new StudioAssetBankCompositionApplyFailedEvent(
                    assetReference,
                    exception.getMessage())));
        }
    }

    private AssetWorkspaceBankCompositionDetails mapBankComposition(PackerBankCompositionDetailsDTO details) {
        return new AssetWorkspaceBankCompositionDetails(
                details.availableFiles().stream().map(this::mapBankCompositionFile).toList(),
                details.selectedFiles().stream().map(this::mapBankCompositionFile).toList(),
                details.measuredBankSizeBytes());
    }

    private AssetWorkspaceBankCompositionFile mapBankCompositionFile(PackerBankCompositionFileDTO file) {
        return new AssetWorkspaceBankCompositionFile(
                file.path(),
                file.displayName(),
                file.size(),
                file.lastModified(),
                file.fingerprint(),
                file.metadata());
    }

    @Override
    protected void reset() {
        coordinator.reset();
        publishStateNotifications();
    }

    @Override
    protected void cancel() {
        coordinator.cancel();
        publishEditScope(workspaceBus, currentScopeKey(), null);
        publishStateNotifications();
    }

    private void publishStateNotifications() {
        if (viewState == null || viewState.selectedAssetReference() == null || !coordinator.ready()) {
            return;
        }
        final AssetDetailsBankCompositionViewModel viewModel = coordinator.viewModel();
        workspaceBus.publish(new StudioAssetBankCompositionDraftChangedEvent(
                viewState.selectedAssetReference(),
                viewModel.editing(),
                viewModel.dirty(),
                viewModel.availableFiles().size(),
                viewModel.selectedFiles().size()));
        workspaceBus.publish(new StudioAssetBankCompositionCapacityChangedEvent(
                viewState.selectedAssetReference(),
                viewModel.capacityState().progress(),
                viewModel.capacityState().severity(),
                viewModel.capacityState().blocked(),
                viewModel.capacityState().labelText(),
                viewModel.capacityState().hintText()));
    }

    private String currentScopeKey() {
        return viewState == null || viewState.selectedAssetReference() == null
                ? null
                : "asset-details:" + viewState.selectedAssetReference();
    }
}
