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

import javafx.application.Platform;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import p.packer.events.PackerEventKind;
import p.packer.messages.ApplyPaletteOverhaulingRequest;
import p.studio.Container;
import p.studio.controls.forms.StudioFormEditScopeChangedEvent;
import p.studio.controls.forms.StudioFormMode;
import p.studio.controls.forms.StudioFormSection;
import p.studio.lsp.events.StudioPackerOperationEvent;
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.events.StudioAssetLogEvent;
import p.studio.workspaces.assets.messages.events.StudioAssetsDetailsViewStateChangedEvent;
import p.studio.workspaces.assets.messages.events.StudioAssetsWorkspaceSelectionRequestedEvent;
import p.studio.workspaces.assets.messages.events.StudioGlyphMaterialMetadataChangedEvent;
import p.studio.workspaces.assets.metadata.AssetStudioGlyphMaterialMetadata;
import p.studio.workspaces.assets.metadata.AssetStudioMetadataService;
import p.studio.workspaces.framework.StudioSubscriptionBag;

import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

public final class AssetDetailsPaletteOverhaulingControl extends StudioFormSection {
    private static final String SECTION_ID = "asset-details.palette-overhauling";

    private final ProjectReference projectReference;
    private final StudioWorkspaceEventBus workspaceBus;
    private final StudioSubscriptionBag subscriptions = new StudioSubscriptionBag();
    private final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
    private final AssetStudioMetadataService metadataService = new AssetStudioMetadataService();
    private final AssetDetailsPaletteOverhaulingDualListView dualListView = new AssetDetailsPaletteOverhaulingDualListView();
    private final AssetDetailsPaletteOverhaulingPreviewPane previewPane = new AssetDetailsPaletteOverhaulingPreviewPane();
    private final VBox paletteActions = new VBox(6);
    private final HBox listWithActions = new HBox(8, paletteActions, dualListView);
    private final HBox body = new HBox(16, listWithActions, previewPane);
    private final VBox content = new VBox(12, body);

    private AssetWorkspaceDetailsViewState viewState;

    public AssetDetailsPaletteOverhaulingControl(ProjectReference projectReference, StudioWorkspaceEventBus workspaceBus) {
        this.projectReference = Objects.requireNonNull(projectReference, "projectReference");
        this.workspaceBus = Objects.requireNonNull(workspaceBus, "workspaceBus");
        body.getStyleClass().add("assets-details-palette-overhauling-body");
        paletteActions.getStyleClass().add("assets-details-palette-overhauling-toolbar");
        listWithActions.getStyleClass().add("assets-details-list-with-actions");
        content.setFillWidth(true);
        HBox.setHgrow(listWithActions, Priority.ALWAYS);
        HBox.setHgrow(previewPane, Priority.ALWAYS);
        HBox.setHgrow(dualListView, Priority.ALWAYS);
        dualListView.setMaxWidth(Double.MAX_VALUE);
        previewPane.setMaxWidth(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();
        }));
        subscriptions.add(workspaceBus.subscribe(StudioFormEditScopeChangedEvent.class,
                event -> handleEditScopeChanged(event, currentScopeKey())));
        subscriptions.add(workspaceBus.subscribe(StudioGlyphMaterialMetadataChangedEvent.class, event -> {
            if (viewState == null || !Objects.equals(viewState.selectedAssetReference(), event.assetReference())) {
                return;
            }
            coordinator.replaceMaterialMetadata(event.metadata());
            renderSection();
        }));
    }

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

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

        final AssetDetailsPaletteOverhaulingViewModel viewModel = coordinator.viewModel();
        dualListView.setLeftItems(viewModel.availableFiles());
        dualListView.setRightItems(viewModel.selectedFiles());
        dualListView.setInteractionEnabled(viewModel.editing());
        dualListView.setMoveToRightAllowed(viewModel.selectedFiles().size() < 64);
        dualListView.setOnMoveToRight(items -> {
            coordinator.moveToSelected(items);
            rerenderPreservingScrollPosition();
        });
        dualListView.setOnMoveToLeft(items -> {
            coordinator.moveToAvailable(items);
            rerenderPreservingScrollPosition();
        });
        dualListView.setOnMoveUp(index -> { });
        dualListView.setOnMoveDown(index -> { });
        dualListView.setOnLeftSelectionChanged(item -> {
            if (item != null) {
                coordinator.selectPreviewPalette(item);
                previewPane.render(
                        coordinator.viewModel().previewTileFiles(),
                        coordinator.viewModel().previewTileFile(),
                        coordinator.viewModel().previewPaletteFile(),
                        coordinator.viewModel().renderableFileCount());
            }
        });
        dualListView.setOnRightSelectionChanged(item -> {
            if (item != null) {
                coordinator.selectPreviewPalette(item);
                previewPane.render(
                        coordinator.viewModel().previewTileFiles(),
                        coordinator.viewModel().previewTileFile(),
                        coordinator.viewModel().previewPaletteFile(),
                        coordinator.viewModel().renderableFileCount());
            }
        });
        dualListView.setOnLeftItemActivated(item -> openPaletteWizard(item));
        dualListView.setOnRightItemActivated(item -> openPaletteWizard(item));
        if (viewModel.previewPaletteFile() != null) {
            if (viewModel.selectedFiles().contains(viewModel.previewPaletteFile())) {
                dualListView.selectRightItem(viewModel.previewPaletteFile());
            } else {
                dualListView.selectLeftItem(viewModel.previewPaletteFile());
            }
        }

        previewPane.setOnPreviewTileChanged(file -> {
            if (file != null) {
                coordinator.selectPreviewTile(file);
                previewPane.render(
                        coordinator.viewModel().previewTileFiles(),
                        coordinator.viewModel().previewTileFile(),
                        coordinator.viewModel().previewPaletteFile(),
                        coordinator.viewModel().renderableFileCount());
            }
        });
        previewPane.render(
                viewModel.previewTileFiles(),
                viewModel.previewTileFile(),
                viewModel.previewPaletteFile(),
                viewModel.renderableFileCount());

        final Button addButton = new Button("+");
        addButton.getStyleClass().addAll("studio-button", "studio-button-secondary", "assets-details-mini-action-button");
        addButton.setDisable(!viewModel.editing() || viewState == null || viewState.selectedAssetDetails() == null);
        addButton.setOnAction(ignored -> openPaletteWizard(null));

        final Button removeButton = new Button("-");
        removeButton.getStyleClass().addAll("studio-button", "studio-button-secondary", "assets-details-mini-action-button");
        removeButton.setDisable(!viewModel.editing() || viewModel.previewPaletteFile() == null);
        removeButton.setOnAction(ignored -> removePalette(coordinator.viewModel().previewPaletteFile()));
        paletteActions.getChildren().setAll(addButton, removeButton);

        renderFormSection(content);
    }

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

    @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 void beginEdit() {
        coordinator.beginEdit();
        publishEditScope(workspaceBus, currentScopeKey(), SECTION_ID);
    }

    @Override
    protected void apply() {
        if (viewState == null || viewState.selectedAssetReference() == null || !coordinator.ready()) {
            return;
        }
        final var selectedPalettes = coordinator.viewModel().selectedFiles().stream()
                .map(file -> palettePayload(file.metadata()))
                .filter(Objects::nonNull)
                .toList();
        if (selectedPalettes.isEmpty()) {
            workspaceBus.publish(new StudioAssetLogEvent("palette-overhauling", "Apply failed: no palette was selected."));
            Container.eventBus().publish(new StudioPackerOperationEvent(
                    UUID.randomUUID().toString(),
                    PackerEventKind.ACTION_FAILED,
                    "Palette overhauling apply failed: no palette was selected.",
                    null,
                    true));
            return;
        }

        final var assetReference = viewState.selectedAssetReference();
        Container.backgroundTasks().submit(() -> {
            final var request = new ApplyPaletteOverhaulingRequest(
                    projectReference.toPackerProjectContext(),
                    assetReference,
                    selectedPalettes);
            try {
                final var response = Container.packer().workspaceService().applyPaletteOverhauling(request);
                Platform.runLater(() -> {
                    if (response.success()) {
                        coordinator.apply();
                        publishEditScope(workspaceBus, currentScopeKey(), null);
                        renderSection();
                        workspaceBus.publish(new StudioAssetsWorkspaceSelectionRequestedEvent(assetReference, true));
                    } else {
                        workspaceBus.publish(new StudioAssetLogEvent("palette-overhauling",
                                "Apply failed: " + Objects.requireNonNullElse(response.errorMessage(), "unknown error")));
                    }
                });
            } catch (Exception exception) {
                Platform.runLater(() -> workspaceBus.publish(new StudioAssetLogEvent(
                        "palette-overhauling",
                        "Apply failed: " + Objects.requireNonNullElse(exception.getMessage(), "unknown error"))));
            }
        });
    }

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

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

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

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

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

    private void openPaletteWizard(p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile paletteFile) {
        if (getScene() == null || !coordinator.ready() || viewState == null || viewState.selectedAssetDetails() == null) {
            return;
        }
        if (AssetDetailsPaletteOverhaulingCoordinator.paletteIsOriginal(paletteFile)) {
            return;
        }
        final var result = AssetDetailsPaletteWizardDialog.showAndWait(
                getScene().getWindow(),
                coordinator,
                paletteFile);
        result.ifPresent(edit -> Container.backgroundTasks().submit(() -> {
            try {
                final AssetStudioGlyphMaterialMetadata metadata = coordinator.upsertPalette(
                        edit.paletteId(),
                        edit.displayName(),
                        edit.schemaId(),
                        edit.colors());
                metadataService.writeGlyphMaterialMetadata(viewState.selectedAssetDetails().summary().assetRoot(), metadata);
                Platform.runLater(() -> {
                    workspaceBus.publish(new StudioAssetLogEvent("palette-overhauling", "Palette saved: " + edit.paletteId()));
                    workspaceBus.publish(new StudioGlyphMaterialMetadataChangedEvent(viewState.selectedAssetReference(), metadata));
                });
            } catch (IOException exception) {
                Platform.runLater(() -> workspaceBus.publish(new StudioAssetLogEvent(
                        "palette-overhauling",
                        "Unable to save palette: " + Objects.requireNonNullElse(exception.getMessage(), "unknown error"))));
            }
        }));
    }

    private void removePalette(p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile paletteFile) {
        if (paletteFile == null || viewState == null || viewState.selectedAssetDetails() == null || viewState.selectedAssetReference() == null) {
            return;
        }
        if (AssetDetailsPaletteOverhaulingCoordinator.paletteIsOriginal(paletteFile)) {
            workspaceBus.publish(new StudioAssetLogEvent(
                    "palette-overhauling",
                    "Original schema palettes cannot be removed."));
            return;
        }
        final Object paletteIdValue = paletteFile.metadata().get("paletteId");
        if (!(paletteIdValue instanceof String paletteId) || paletteId.isBlank()) {
            return;
        }
        Container.backgroundTasks().submit(() -> {
            try {
                final AssetStudioGlyphMaterialMetadata metadata = coordinator.removePalette(paletteId);
                metadataService.writeGlyphMaterialMetadata(viewState.selectedAssetDetails().summary().assetRoot(), metadata);
                Platform.runLater(() -> {
                    workspaceBus.publish(new StudioAssetLogEvent("palette-overhauling", "Palette removed: " + paletteId));
                    workspaceBus.publish(new StudioGlyphMaterialMetadataChangedEvent(viewState.selectedAssetReference(), metadata));
                });
            } catch (Exception exception) {
                Platform.runLater(() -> workspaceBus.publish(new StudioAssetLogEvent(
                        "palette-overhauling",
                        "Unable to remove palette: " + Objects.requireNonNullElse(exception.getMessage(), "unknown error"))));
            }
        });
    }

    @SuppressWarnings("unchecked")
    private static Map<String, Object> palettePayload(Map<String, Object> metadata) {
        final Object palette = metadata.get("palette");
        return palette instanceof Map<?, ?> paletteMap ? (Map<String, Object>) paletteMap : null;
    }
}
