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

import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import p.packer.messages.ListAssetsRequest;
import p.packer.messages.assets.AssetFamilyCatalog;
import p.studio.Container;
import p.studio.controls.forms.StudioFormMode;
import p.studio.controls.lifecycle.StudioControlLifecycle;
import p.studio.controls.lifecycle.StudioControlLifecycleSupport;
import p.studio.lsp.events.StudioWorkspaceEventBus;
import p.studio.projects.ProjectReference;
import p.studio.utilities.i18n.I18n;
import p.studio.workspaces.assets.details.AssetDetailsUiSupport;
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.StudioAssetsWorkspaceDetailsSceneLayerChangedEvent;
import p.studio.workspaces.assets.messages.events.StudioAssetsWorkspaceSelectionRequestedEvent;
import p.studio.workspaces.assets.metadata.*;
import p.studio.workspaces.framework.StudioSubscriptionBag;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;

public final class AssetDetailsSceneLayersControl extends VBox implements StudioControlLifecycle {
    private final ProjectReference projectReference;
    private final StudioWorkspaceEventBus workspaceBus;
    private final StudioSubscriptionBag subscriptions = new StudioSubscriptionBag();
    private final AssetStudioMetadataService metadataService = new AssetStudioMetadataService();

    private AssetWorkspaceDetailsViewState viewState;
    private Integer editingLayerIndex;
    private LayerDraft editingDraft;
    private int selectedLayerIndex = 1;

    public AssetDetailsSceneLayersControl(ProjectReference projectReference, StudioWorkspaceEventBus workspaceBus) {
        StudioControlLifecycleSupport.install(this, this);
        this.projectReference = Objects.requireNonNull(projectReference, "projectReference");
        this.workspaceBus = Objects.requireNonNull(workspaceBus, "workspaceBus");
    }

    @Override
    public void subscribe() {
        subscriptions.add(workspaceBus.subscribe(StudioAssetsDetailsViewStateChangedEvent.class, event -> {
            viewState = event.viewState();
            render();
        }));
        subscriptions.add(workspaceBus.subscribe(StudioAssetsWorkspaceDetailsSceneLayerChangedEvent.class, event -> {
            viewState = event.viewState();
            render();
        }));
    }

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

    private void render() {
        final AssetStudioSceneBankMetadata metadata = viewState == null || viewState.selectedAssetDetails() == null
                ? null
                : viewState.selectedAssetDetails().sceneBankMetadata();
        if (metadata == null) {
            getChildren().clear();
            return;
        }

        final VBox content = new VBox(10);
        content.getChildren().add(createLayerSelector(
                viewState.selectedAssetDetails().summary().assetRoot(),
                metadata));
        content.getStyleClass().add("assets-scene-layer-shell");

        getChildren().setAll(AssetDetailsUiSupport.createSectionWithoutHeader(content));
    }

    private Node createLayerSelector(Path sceneAssetRoot, AssetStudioSceneBankMetadata metadata) {
        if (selectedLayerIndex < 1 || selectedLayerIndex > metadata.layerCount()) {
            selectedLayerIndex = 1;
        }
        final HBox selector = new HBox(8);
        selector.getStyleClass().add("assets-scene-layer-selector");
        for (int index = 1; index <= 4; index += 1) {
            final int layerIndex = index;
            final Button button = new Button();
            button.textProperty().bind(Container.i18n().bind(I18n.ASSETS_LABEL_LAYER, layerIndex));
            button.getStyleClass().addAll("studio-button", "assets-scene-layer-selector-button");
            if (layerIndex == selectedLayerIndex) {
                button.getStyleClass().add("assets-scene-layer-selector-button-active");
            }
            button.setOnAction(ignored -> {
                selectedLayerIndex = layerIndex;
                render();
            });
            selector.getChildren().add(button);
        }

        final AssetStudioSceneLayerBinding binding = metadata.layerBindings().stream()
                .filter(candidate -> candidate.index() == selectedLayerIndex)
                .findFirst()
                .orElse(null);
        final VBox container = new VBox(10);
        container.getChildren().addAll(selector, createLayerCard(sceneAssetRoot, selectedLayerIndex, binding));
        return container;
    }

    private Node createLayerCard(Path sceneAssetRoot, int layerIndex, AssetStudioSceneLayerBinding binding) {
        final boolean editing = Objects.equals(editingLayerIndex, layerIndex) && binding != null && editingDraft != null;
        final LayerDraft draft = editing ? editingDraft : LayerDraft.from(binding);
        final VBox formContent = buildLayerFormContent(sceneAssetRoot, binding, draft, editing);
        final VBox formSection = AssetDetailsUiSupport.createEditableSection(
                Container.i18n().text(I18n.ASSETS_SECTION_LAYER_CONFIGURATION),
                createLayerFormHeaderActions(layerIndex, binding, editing),
                formContent,
                editing ? StudioFormMode.EDITING : StudioFormMode.READ_ONLY);
        formSection.getStyleClass().add("assets-scene-layer-config-section");

        final VBox leftColumn = new VBox(10);
        leftColumn.getStyleClass().add("assets-scene-layer-config-column");
        leftColumn.getChildren().addAll(
                formSection,
                createLayerActions(sceneAssetRoot, layerIndex, draft));

        final VBox overview = createLayerOverviewPlaceholder();
        final HBox layout = new HBox(12, leftColumn, overview);
        layout.getStyleClass().add("assets-scene-layer-layout");
        HBox.setHgrow(leftColumn, Priority.ALWAYS);
        return layout;
    }

    private VBox buildLayerFormContent(Path sceneAssetRoot, AssetStudioSceneLayerBinding binding, LayerDraft draft, boolean editing) {
        final VBox card = new VBox(8);
        card.getStyleClass().addAll("assets-scene-layer-card", "assets-scene-layer-config");

        final CheckBox enabledCheckBox = new CheckBox();
        enabledCheckBox.textProperty().bind(Container.i18n().bind(I18n.ASSETS_LABEL_USE_LAYER));
        enabledCheckBox.setSelected(draft != null && draft.enabled());
        enabledCheckBox.getStyleClass().add("assets-scene-layer-enabled");
        enabledCheckBox.setDisable(!editing);
        enabledCheckBox.setOnAction(ignored -> {
            if (draft != null) {
                draft.setEnabled(enabledCheckBox.isSelected());
            }
        });
        card.getChildren().add(enabledCheckBox);

        card.getChildren().addAll(
                AssetDetailsUiSupport.createKeyValueRow(
                        Container.i18n().text(I18n.ASSETS_LABEL_SCENE_DIMENSIONS),
                        editing ? createLayerSizeEditor(draft) : new Label(draft.widthText() + " x " + draft.heightText())),
                AssetDetailsUiSupport.createKeyValueRow(
                        Container.i18n().text(I18n.ASSETS_LABEL_TILEMAP),
                        binding == null
                                ? Container.i18n().text(I18n.ASSETS_VALUE_NOT_CONFIGURED)
                                : AssetDetailsUiSupport.projectRelativePath(projectReference, sceneAssetRoot.resolve(binding.tilemap()))),
                AssetDetailsUiSupport.createKeyValueRow(
                        Container.i18n().text(I18n.ASSETS_LABEL_TILESET),
                        editing ? createTilesetCombo(draft, true) : createTilesetReadOnlyValue(binding, draft)),
                AssetDetailsUiSupport.createKeyValueRow(
                        Container.i18n().text(I18n.ASSETS_LABEL_PARALLAX),
                        editing ? createParallaxEditor(draft) : new Label(draft.parallaxText())),
                AssetDetailsUiSupport.createKeyValueRow(
                        Container.i18n().text(I18n.ASSETS_LABEL_DEFAULT_PALETTE),
                        editing ? createDefaultPaletteEditor(draft) : new Label(draft.defaultPaletteText())));
        return card;
    }

    private VBox createLayerOverviewPlaceholder() {
        final VBox overview = new VBox(10);
        overview.getStyleClass().add("assets-scene-layer-overview");

        final Label title = new Label(Container.i18n().text(I18n.ASSETS_LABEL_LAYER_OVERVIEW));
        title.getStyleClass().add("assets-scene-layer-overview-title");

        final Region surface = new Region();
        surface.getStyleClass().add("assets-scene-layer-overview-surface");
        VBox.setVgrow(surface, Priority.ALWAYS);

        overview.getChildren().addAll(
                title,
                surface,
                AssetDetailsUiSupport.createSectionMessage(Container.i18n().text(I18n.ASSETS_VALUE_LAYER_OVERVIEW_PENDING)));
        return overview;
    }

    private Node createLayerFormHeaderActions(int layerIndex, AssetStudioSceneLayerBinding binding, boolean editing) {
        final HBox actions = new HBox(8);
        actions.getStyleClass().add("assets-scene-layer-form-actions");
        if (!editing) {
            final Button changeButton = createFormActionButton(I18n.FORM_ACTION_CHANGE);
            changeButton.setDisable(binding == null);
            changeButton.setOnAction(ignored -> beginEdit(layerIndex, binding));
            actions.getChildren().add(changeButton);
            return actions;
        }

        final Button applyButton = createFormActionButton(I18n.FORM_ACTION_APPLY);
        applyButton.setOnAction(ignored -> applyEdit(layerIndex, binding));
        final Button resetButton = createFormActionButton(I18n.FORM_ACTION_RESET);
        resetButton.setOnAction(ignored -> resetEdit(binding));
        final Button cancelButton = createFormActionButton(I18n.FORM_ACTION_CANCEL);
        cancelButton.setOnAction(ignored -> cancelEdit());
        actions.getChildren().addAll(applyButton, resetButton, cancelButton);
        return actions;
    }

    private Node createLayerActions(Path sceneAssetRoot, int layerIndex, LayerDraft draft) {
        final HBox actions = new HBox(8);
        actions.setAlignment(Pos.CENTER_LEFT);
        actions.getStyleClass().add("assets-scene-layer-actions");
        final Region spacer = new Region();
        HBox.setHgrow(spacer, Priority.ALWAYS);
        actions.getChildren().add(spacer);
        return actions;
    }

    private Node createLayerSizeEditor(LayerDraft draft) {
        final HBox editor = new HBox(8);
        editor.getStyleClass().add("assets-scene-layer-size-editor");

        final TextField widthField = createSizeField(draft.widthText(), draft::setWidthText);
        final Label separator = new Label("x");
        separator.getStyleClass().add("assets-scene-layer-size-separator");
        final TextField heightField = createSizeField(draft.heightText(), draft::setHeightText);

        editor.getChildren().addAll(widthField, separator, heightField);
        return editor;
    }

    private TextField createSizeField(String value, java.util.function.Consumer<String> updater) {
        final TextField textField = new TextField(value);
        textField.getStyleClass().add("assets-scene-layer-size-field");
        textField.textProperty().addListener((ignored, oldValue, newValue) -> updater.accept(newValue));
        return textField;
    }

    private ComboBox<TilesetOption> createTilesetCombo(LayerDraft draft, boolean editing) {
        final ComboBox<TilesetOption> combo = new ComboBox<>();
        combo.setMaxWidth(Double.MAX_VALUE);
        combo.getStyleClass().add("assets-details-combo");
        final List<TilesetOption> options = loadTilesetOptions();
        combo.setItems(FXCollections.observableArrayList(options));
        combo.setCellFactory(ignored -> new ListCell<>() {
            @Override
            protected void updateItem(TilesetOption item, boolean empty) {
                super.updateItem(item, empty);
                setText(empty || item == null ? null : item.label());
            }
        });
        combo.setButtonCell(new ListCell<>() {
            @Override
            protected void updateItem(TilesetOption item, boolean empty) {
                super.updateItem(item, empty);
                setText(empty || item == null ? null : item.label());
            }
        });
        final String currentTileset = draft == null ? "" : draft.tilesetAssetRoot();
        options.stream()
                .filter(option -> option.relativeAssetRoot().equals(currentTileset))
                .findFirst()
                .ifPresentOrElse(
                        combo.getSelectionModel()::select,
                        () -> combo.getSelectionModel().clearSelection());
        combo.setDisable(!editing || draft == null);
        if (draft == null) {
            combo.setDisable(true);
            return combo;
        }
        combo.valueProperty().addListener((ignored, oldValue, newValue) -> {
            if (Objects.equals(oldValue, newValue)) {
                return;
            }
            draft.setTilesetAssetRoot(newValue == null ? "" : newValue.relativeAssetRoot());
        });
        return combo;
    }

    private Node createParallaxEditor(LayerDraft draft) {
        final HBox editor = new HBox(8);
        editor.getStyleClass().add("assets-scene-layer-size-editor");
        final TextField xField = createSizeField(draft.parallaxXText(), draft::setParallaxXText);
        final Label separator = new Label(",");
        separator.getStyleClass().add("assets-scene-layer-size-separator");
        final TextField yField = createSizeField(draft.parallaxYText(), draft::setParallaxYText);
        editor.getChildren().addAll(xField, separator, yField);
        return editor;
    }

    private Node createDefaultPaletteEditor(LayerDraft draft) {
        final TextField textField = new TextField(draft.defaultPaletteText());
        textField.getStyleClass().add("assets-scene-layer-size-field");
        textField.textProperty().addListener((ignored, oldValue, newValue) -> draft.setDefaultPaletteText(newValue));
        return textField;
    }

    private Node createTilesetReadOnlyValue(AssetStudioSceneLayerBinding binding, LayerDraft draft) {
        final String value = binding == null || draft == null || draft.tilesetAssetRoot().isBlank()
                ? Container.i18n().text(I18n.ASSETS_VALUE_NOT_CONFIGURED)
                : tilesetPath(draft.tilesetAssetRoot());
        return new Label(value);
    }

    private List<TilesetOption> loadTilesetOptions() {
        try {
            final var response = Container.packer().workspaceService().listAssets(new ListAssetsRequest(projectReference.toPackerProjectContext()));
            return response.assets().stream()
                    .filter(asset -> asset.assetFamily() == AssetFamilyCatalog.GLYPH_BANK)
                    .filter(asset -> metadataService
                            .read(asset.identity().assetRoot(), AssetFamilyCatalog.GLYPH_BANK)
                            .glyphSpecialization() == AssetStudioGlyphSpecialization.TILESET)
                    .map(asset -> {
                        final String relativeAssetRoot = projectReference.rootPath()
                                .resolve("assets")
                                .relativize(asset.identity().assetRoot())
                                .toString()
                                .replace('\\', '/');
                        return new TilesetOption(asset.identity().assetName(), relativeAssetRoot);
                    })
                    .sorted(Comparator.comparing(TilesetOption::label, String.CASE_INSENSITIVE_ORDER))
                    .toList();
        } catch (RuntimeException exception) {
            return List.of();
        }
    }

    private void updateLayerBinding(
            int layerIndex,
            java.util.function.Function<AssetStudioSceneLayerBinding, AssetStudioSceneLayerBinding> mutator,
            String successMessage) {
        if (viewState == null || viewState.selectedAssetDetails() == null || viewState.selectedAssetDetails().sceneBankMetadata() == null) {
            return;
        }
        final AssetStudioSceneBankMetadata metadata = viewState.selectedAssetDetails().sceneBankMetadata();
        final java.util.List<AssetStudioSceneLayerBinding> updatedBindings = new ArrayList<>();
        for (AssetStudioSceneLayerBinding binding : metadata.layerBindings()) {
            if (binding.index() == layerIndex) {
                updatedBindings.add(mutator.apply(binding));
            } else {
                updatedBindings.add(binding);
            }
        }
        final AssetStudioSceneLayerBinding firstBinding = updatedBindings.getFirst();
        final AssetStudioSceneBankMetadata updatedMetadata = new AssetStudioSceneBankMetadata(
                firstBinding.width(),
                firstBinding.height(),
                metadata.layerCount(),
                updatedBindings,
                metadata.supportFile());
        Container.backgroundTasks().submit(() -> {
            try {
                metadataService.writeSceneBankMetadata(viewState.selectedAssetDetails().summary().assetRoot(), updatedMetadata);
                Platform.runLater(() -> {
                    workspaceBus.publish(new StudioAssetLogEvent("scene-bank-layer", successMessage));
                    workspaceBus.publish(new StudioAssetsWorkspaceSelectionRequestedEvent(viewState.selectedAssetReference(), true));
                });
            } catch (IOException exception) {
                Platform.runLater(() -> workspaceBus.publish(
                        new StudioAssetLogEvent("scene-bank-layer", "Unable to update layer " + layerIndex + ": " + exception.getMessage())));
            }
        });
    }

    private String tilesetPath(AssetStudioSceneLayerBinding binding) {
        return tilesetPath(binding.tilesetAssetRoot());
    }

    private String tilesetPath(String tilesetAssetRoot) {
        if (tilesetAssetRoot.isBlank()) {
            return "—";
        }
        return AssetDetailsUiSupport.projectRelativePath(
                projectReference,
                projectReference.rootPath().resolve("assets").resolve(tilesetAssetRoot));
    }

    private void beginEdit(int layerIndex, AssetStudioSceneLayerBinding binding) {
        editingLayerIndex = layerIndex;
        editingDraft = LayerDraft.from(binding);
        render();
    }

    private void applyEdit(int layerIndex, AssetStudioSceneLayerBinding binding) {
        if (binding == null || editingDraft == null || !editingDraft.hasValidState()) {
            workspaceBus.publish(new StudioAssetLogEvent(
                    "scene-bank-layer",
                    "Layer configuration must use positive dimensions, non-negative parallax, and a non-negative default palette before applying changes."));
            return;
        }
        final LayerDraft draft = editingDraft.copy();
        editingLayerIndex = null;
        editingDraft = null;
        render();
        updateLayerBinding(layerIndex, current -> new AssetStudioSceneLayerBinding(
                draft.enabled(),
                current.index(),
                current.layerName(),
                draft.widthValue(),
                draft.heightValue(),
                current.tilemap(),
                draft.tilesetAssetRoot(),
                draft.parallaxXValue(),
                draft.parallaxYValue(),
                draft.defaultPaletteValue(),
                preservedTilesOrBlank(current, draft)),
                "Updated layer " + layerIndex + " configuration.");
    }

    private List<AssetStudioSceneTile> preservedTilesOrBlank(
            AssetStudioSceneLayerBinding current,
            LayerDraft draft) {
        if (current.width() == draft.widthValue() && current.height() == draft.heightValue()) {
            return current.tiles();
        }
        final int tileCount = draft.widthValue() * draft.heightValue();
        final List<AssetStudioSceneTile> tiles = new ArrayList<>(tileCount);
        for (int index = 0; index < tileCount; index += 1) {
            tiles.add(AssetStudioSceneTile.inactive(draft.defaultPaletteValue()));
        }
        return List.copyOf(tiles);
    }

    private void resetEdit(AssetStudioSceneLayerBinding binding) {
        editingDraft = LayerDraft.from(binding);
        render();
    }

    private void cancelEdit() {
        editingLayerIndex = null;
        editingDraft = null;
        render();
    }

    private Button createFormActionButton(I18n textKey) {
        final Button button = new Button(Container.i18n().text(textKey));
        button.getStyleClass().addAll("studio-button", "studio-button-secondary");
        return button;
    }

    private boolean hasLayerTilemap(Path sceneAssetRoot, int layerIndex) {
        if (viewState == null || viewState.selectedAssetDetails() == null || viewState.selectedAssetDetails().sceneBankMetadata() == null) {
            return false;
        }
        return viewState.selectedAssetDetails().sceneBankMetadata().layerBindings().stream()
                .filter(binding -> binding.index() == layerIndex)
                .findFirst()
                .map(binding -> Files.isRegularFile(sceneAssetRoot.resolve(binding.tilemap())))
                .orElse(false);
    }

    private record TilesetOption(String assetName, String relativeAssetRoot) {
        String label() {
            return assetName + " [" + relativeAssetRoot + "]";
        }
    }

    private static final class LayerDraft {
        private boolean enabled;
        private String widthText;
        private String heightText;
        private String tilesetAssetRoot;
        private String parallaxXText;
        private String parallaxYText;
        private String defaultPaletteText;

        private LayerDraft(
                boolean enabled,
                String widthText,
                String heightText,
                String tilesetAssetRoot,
                String parallaxXText,
                String parallaxYText,
                String defaultPaletteText) {
            this.enabled = enabled;
            this.widthText = widthText;
            this.heightText = heightText;
            this.tilesetAssetRoot = Objects.requireNonNullElse(tilesetAssetRoot, "").trim();
            this.parallaxXText = Objects.requireNonNullElse(parallaxXText, "").trim();
            this.parallaxYText = Objects.requireNonNullElse(parallaxYText, "").trim();
            this.defaultPaletteText = Objects.requireNonNullElse(defaultPaletteText, "").trim();
        }

        static LayerDraft from(AssetStudioSceneLayerBinding binding) {
            if (binding == null) {
                return new LayerDraft(false, "16", "16", "", "1.0", "1.0", "0");
            }
            return new LayerDraft(
                    binding.enabled(),
                    Integer.toString(binding.width()),
                    Integer.toString(binding.height()),
                    binding.tilesetAssetRoot(),
                    trimFloat(binding.parallaxFactorX()),
                    trimFloat(binding.parallaxFactorY()),
                    Integer.toString(binding.defaultPaletteId()));
        }

        LayerDraft copy() {
            return new LayerDraft(enabled, widthText, heightText, tilesetAssetRoot, parallaxXText, parallaxYText, defaultPaletteText);
        }

        boolean enabled() {
            return enabled;
        }

        void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }

        String widthText() {
            return widthText;
        }

        void setWidthText(String widthText) {
            this.widthText = Objects.requireNonNullElse(widthText, "").trim();
        }

        String heightText() {
            return heightText;
        }

        void setHeightText(String heightText) {
            this.heightText = Objects.requireNonNullElse(heightText, "").trim();
        }

        String tilesetAssetRoot() {
            return tilesetAssetRoot;
        }

        void setTilesetAssetRoot(String tilesetAssetRoot) {
            this.tilesetAssetRoot = Objects.requireNonNullElse(tilesetAssetRoot, "").trim();
        }

        String parallaxXText() {
            return parallaxXText;
        }

        void setParallaxXText(String parallaxXText) {
            this.parallaxXText = Objects.requireNonNullElse(parallaxXText, "").trim();
        }

        String parallaxYText() {
            return parallaxYText;
        }

        void setParallaxYText(String parallaxYText) {
            this.parallaxYText = Objects.requireNonNullElse(parallaxYText, "").trim();
        }

        String parallaxText() {
            return parallaxXText + ", " + parallaxYText;
        }

        String defaultPaletteText() {
            return defaultPaletteText;
        }

        void setDefaultPaletteText(String defaultPaletteText) {
            this.defaultPaletteText = Objects.requireNonNullElse(defaultPaletteText, "").trim();
        }

        boolean hasValidState() {
            return parsePositiveInt(widthText) > 0
                    && parsePositiveInt(heightText) > 0
                    && parseNonNegativeFloat(parallaxXText) >= 0.0f
                    && parseNonNegativeFloat(parallaxYText) >= 0.0f
                    && parseNonNegativeInt(defaultPaletteText) >= 0;
        }

        int widthValue() {
            return parsePositiveInt(widthText);
        }

        int heightValue() {
            return parsePositiveInt(heightText);
        }

        float parallaxXValue() {
            return parseNonNegativeFloat(parallaxXText);
        }

        float parallaxYValue() {
            return parseNonNegativeFloat(parallaxYText);
        }

        int defaultPaletteValue() {
            return parseNonNegativeInt(defaultPaletteText);
        }

        private static int parsePositiveInt(String value) {
            try {
                return Integer.parseInt(Objects.requireNonNullElse(value, "").trim());
            } catch (NumberFormatException exception) {
                return -1;
            }
        }

        private static int parseNonNegativeInt(String value) {
            try {
                final int parsed = Integer.parseInt(Objects.requireNonNullElse(value, "").trim());
                return parsed >= 0 ? parsed : -1;
            } catch (NumberFormatException exception) {
                return -1;
            }
        }

        private static float parseNonNegativeFloat(String value) {
            try {
                final float parsed = Float.parseFloat(Objects.requireNonNullElse(value, "").trim());
                return Float.isFinite(parsed) && parsed >= 0.0f ? parsed : -1.0f;
            } catch (NumberFormatException exception) {
                return -1.0f;
            }
        }

        private static String trimFloat(float value) {
            if (value == (long) value) {
                return Long.toString((long) value) + ".0";
            }
            return Float.toString(value);
        }
    }
}
