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

import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import p.packer.dtos.PackerCodecConfigurationFieldDTO;
import p.packer.messages.UpdateAssetContractRequest;
import p.packer.messages.assets.OutputCodecCatalog;
import p.studio.Container;
import p.studio.controls.forms.StudioFormActionBar;
import p.studio.controls.forms.StudioFormMode;
import p.studio.controls.forms.StudioFormSession;
import p.studio.controls.lifecycle.StudioControlLifecycle;
import p.studio.controls.lifecycle.StudioControlLifecycleSupport;
import p.studio.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.AssetWorkspaceAssetDetails;
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.StudioAssetsRefreshRequestedEvent;
import p.studio.workspaces.framework.StudioSubscriptionBag;

import java.util.Objects;

public final class AssetDetailsContractControl extends VBox implements StudioControlLifecycle {
    private final ProjectReference projectReference;
    private final StudioWorkspaceEventBus workspaceBus;
    private final StudioSubscriptionBag subscriptions = new StudioSubscriptionBag();
    private final StudioFormActionBar actionBar = new StudioFormActionBar(
            this::beginEdit,
            this::applyDraft,
            this::resetDraft,
            this::cancelEdit);

    private AssetWorkspaceDetailsViewState viewState;
    private StudioFormSession<AssetContractDraft> formSession;

    public AssetDetailsContractControl(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();
            syncFormSession();
            render();
        }));
    }

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

    private void syncFormSession() {
        if (viewState == null || viewState.selectedAssetDetails() == null) {
            formSession = null;
            return;
        }

        final AssetContractDraft source = AssetContractDraft.fromDetails(viewState.selectedAssetDetails());
        if (formSession == null) {
            formSession = new StudioFormSession<>(source);
            return;
        }

        if (!Objects.equals(formSession.source(), source)) {
            formSession.replaceSource(source);
        }
    }

    private void render() {
        if (viewState == null || viewState.selectedAssetDetails() == null) {
            getChildren().clear();
            return;
        }

        if (formSession == null) {
            syncFormSession();
        }
        if (formSession == null) {
            getChildren().clear();
            return;
        }

        final AssetWorkspaceAssetDetails details = viewState.selectedAssetDetails();
        final AssetContractDraft draft = formSession.draft();
        final boolean editing = formSession.mode() == StudioFormMode.EDITING;

        final VBox generalColumn = new VBox(8);
        generalColumn.getStyleClass().add("assets-details-contract-column");
        generalColumn.getChildren().setAll(
                AssetDetailsUiSupport.createKeyValueRow(
                        Container.i18n().text(I18n.ASSETS_LABEL_PRELOAD),
                        createPreloadEditor(draft, editing)),
                AssetDetailsUiSupport.createKeyValueRow(
                        Container.i18n().text(I18n.ASSETS_LABEL_BANK),
                        draft.bank()),
                createMetadataSection(details, draft, editing));

        final VBox codecColumn = new VBox(10);
        codecColumn.getStyleClass().addAll("assets-details-contract-column", "assets-details-contract-codec-column");
        codecColumn.getChildren().setAll(
                AssetDetailsUiSupport.createKeyValueRow(
                        Container.i18n().text(I18n.ASSETS_LABEL_CODEC),
                        createCodecEditor(details, draft, editing)),
                createCodecConfigurationSection(details, draft, editing));

        final HBox contractBody = new HBox(16);
        contractBody.getStyleClass().add("assets-details-contract-body");
        HBox.setHgrow(generalColumn, Priority.ALWAYS);
        HBox.setHgrow(codecColumn, Priority.ALWAYS);
        generalColumn.prefWidthProperty().bind(contractBody.widthProperty().subtract(16).multiply(0.5d));
        codecColumn.prefWidthProperty().bind(contractBody.widthProperty().subtract(16).multiply(0.5d));
        generalColumn.setMaxWidth(Double.MAX_VALUE);
        codecColumn.setMaxWidth(Double.MAX_VALUE);
        contractBody.getChildren().setAll(generalColumn, codecColumn);

        final VBox content = new VBox(12);
        content.getChildren().setAll(contractBody);

        actionBar.updateState(formSession.mode(), formSession.isDirty());
        content.getChildren().add(actionBar);

        final VBox section = AssetDetailsUiSupport.createSection(
                Container.i18n().text(I18n.ASSETS_SECTION_RUNTIME_CONTRACT),
                content);
        section.getStyleClass().add("assets-details-contract-section");
        getChildren().setAll(section);
    }

    private Node createPreloadEditor(AssetContractDraft draft, boolean editing) {
        final CheckBox checkBox = new CheckBox();
        checkBox.getStyleClass().add("assets-details-readonly-check");
        checkBox.setSelected(draft.preload());
        if (editing) {
            checkBox.selectedProperty().addListener((ignored, oldValue, newValue) -> {
                if (!Objects.equals(oldValue, newValue)) {
                    formSession.updateDraft(current -> current.withPreload(newValue));
                    actionBar.updateState(formSession.mode(), formSession.isDirty());
                }
            });
        } else {
            configureReadOnlyControl(checkBox);
        }
        return checkBox;
    }

    private Node createCodecEditor(
            AssetWorkspaceAssetDetails details,
            AssetContractDraft draft,
            boolean editing) {
        final ComboBox<OutputCodecCatalog> codecCombo = new ComboBox<>(FXCollections.observableArrayList(details.availableOutputCodecs()));
        codecCombo.getStyleClass().add("assets-details-combo");
        codecCombo.setMaxWidth(Double.MAX_VALUE);
        codecCombo.setCellFactory(ignored -> new javafx.scene.control.ListCell<>() {
            @Override
            protected void updateItem(OutputCodecCatalog item, boolean empty) {
                super.updateItem(item, empty);
                setText(empty || item == null ? null : item.displayName());
            }
        });
        codecCombo.setButtonCell(new javafx.scene.control.ListCell<>() {
            @Override
            protected void updateItem(OutputCodecCatalog item, boolean empty) {
                super.updateItem(item, empty);
                setText(empty || item == null ? null : item.displayName());
            }
        });
        codecCombo.getSelectionModel().select(draft.selectedCodec());
        if (editing) {
            codecCombo.valueProperty().addListener((ignored, oldValue, newValue) -> {
                if (newValue != null && !Objects.equals(oldValue, newValue)) {
                    formSession.updateDraft(current -> current.withSelectedCodec(newValue));
                    render();
                }
            });
        } else {
            configureReadOnlyControl(codecCombo);
        }
        return codecCombo;
    }

    private Node createCodecConfigurationSection(
            AssetWorkspaceAssetDetails details,
            AssetContractDraft draft,
            boolean editing) {
        final VBox subsection = new VBox(8);
        subsection.getStyleClass().add("assets-details-contract-metadata");
        final Label title = new Label(Container.i18n().text(I18n.ASSETS_SUBSECTION_CODEC_CONFIGURATION));
        title.getStyleClass().add("assets-details-subsection-title");
        final VBox content = new VBox(8);
        content.getStyleClass().add("assets-details-contract-metadata-content");

        if (draft.selectedCodec() == OutputCodecCatalog.NONE || draft.selectedCodec() == OutputCodecCatalog.UNKNOWN) {
            content.getChildren().add(AssetDetailsUiSupport.createSectionMessage(
                    Container.i18n().text(I18n.ASSETS_DETAILS_CODEC_CONFIGURATION_EMPTY)));
            return createCodecMetadataPane(title, content);
        }

        final var fields = details.codecConfigurationFieldsByCodec().getOrDefault(draft.selectedCodec(), java.util.List.of());
        if (fields.isEmpty()) {
            content.getChildren().add(AssetDetailsUiSupport.createSectionMessage(
                    Container.i18n().text(I18n.ASSETS_DETAILS_CODEC_CONFIGURATION_EMPTY)));
            return createCodecMetadataPane(title, content);
        }

        for (PackerCodecConfigurationFieldDTO field : fields) {
            content.getChildren().add(createCodecFieldRow(field, draft, editing));
        }
        return createCodecMetadataPane(title, content);
    }

    private Node createMetadataSection(
            AssetWorkspaceAssetDetails details,
            AssetContractDraft draft,
            boolean editing) {
        final VBox subsection = new VBox(8);
        subsection.getStyleClass().add("assets-details-contract-metadata");
        final Label title = new Label(Container.i18n().text(I18n.ASSETS_SUBSECTION_METADATA));
        title.getStyleClass().add("assets-details-subsection-title");
        final VBox content = new VBox(8);
        content.getStyleClass().add("assets-details-contract-metadata-content");

        if (details.metadataFields().isEmpty()) {
            content.getChildren().add(AssetDetailsUiSupport.createSectionMessage(
                    Container.i18n().text(I18n.ASSETS_DETAILS_METADATA_EMPTY)));
            return createCodecMetadataPane(title, content);
        }

        for (PackerCodecConfigurationFieldDTO field : details.metadataFields()) {
            content.getChildren().add(createMetadataFieldRow(field, draft, editing));
        }
        return createCodecMetadataPane(title, content);
    }

    private Node createMetadataFieldRow(
            PackerCodecConfigurationFieldDTO field,
            AssetContractDraft draft,
            boolean editing) {
        final Node valueNode = switch (field.fieldType()) {
            case BOOLEAN -> createMetadataBooleanField(field, draft, editing);
            case ENUM -> createMetadataEnumField(field, draft, editing);
            case INTEGER, TEXT -> createMetadataTextField(field, draft, editing);
        };
        return AssetDetailsUiSupport.createKeyValueRow(field.label(), valueNode);
    }

    private Node createMetadataBooleanField(
            PackerCodecConfigurationFieldDTO field,
            AssetContractDraft draft,
            boolean editing) {
        final CheckBox checkBox = new CheckBox();
        checkBox.getStyleClass().add("assets-details-readonly-check");
        checkBox.setSelected(Boolean.parseBoolean(currentMetadataValue(field, draft)));
        if (editing) {
            checkBox.selectedProperty().addListener((ignored, oldValue, newValue) -> {
                if (!Objects.equals(oldValue, newValue)) {
                    formSession.updateDraft(current -> current.withMetadataFieldValue(
                            field.key(),
                            Boolean.toString(newValue)));
                    actionBar.updateState(formSession.mode(), formSession.isDirty());
                }
            });
        } else {
            configureReadOnlyControl(checkBox);
        }
        return checkBox;
    }

    private Node createMetadataEnumField(
            PackerCodecConfigurationFieldDTO field,
            AssetContractDraft draft,
            boolean editing) {
        final ComboBox<String> comboBox = new ComboBox<>(FXCollections.observableArrayList(field.options()));
        comboBox.getStyleClass().add("assets-details-combo");
        comboBox.setMaxWidth(Double.MAX_VALUE);
        comboBox.getSelectionModel().select(currentMetadataValue(field, draft));
        if (editing) {
            comboBox.valueProperty().addListener((ignored, oldValue, newValue) -> {
                if (newValue != null && !Objects.equals(oldValue, newValue)) {
                    formSession.updateDraft(current -> current.withMetadataFieldValue(
                            field.key(),
                            newValue));
                    actionBar.updateState(formSession.mode(), formSession.isDirty());
                }
            });
        } else {
            configureReadOnlyControl(comboBox);
        }
        return comboBox;
    }

    private Node createMetadataTextField(
            PackerCodecConfigurationFieldDTO field,
            AssetContractDraft draft,
            boolean editing) {
        final TextField textField = new TextField(currentMetadataValue(field, draft));
        textField.getStyleClass().add("assets-workspace-search");
        textField.setMaxWidth(Double.MAX_VALUE);
        HBox.setHgrow(textField, Priority.ALWAYS);
        if (editing) {
            textField.textProperty().addListener((ignored, oldValue, newValue) -> {
                if (!Objects.equals(oldValue, newValue)) {
                    formSession.updateDraft(current -> current.withMetadataFieldValue(
                            field.key(),
                            Objects.requireNonNullElse(newValue, "")));
                    actionBar.updateState(formSession.mode(), formSession.isDirty());
                }
            });
        } else {
            textField.setEditable(false);
            configureReadOnlyControl(textField);
        }
        return textField;
    }

    private Node createCodecMetadataPane(Label title, VBox content) {
        final ScrollPane scrollPane = new ScrollPane(content);
        scrollPane.setFitToWidth(true);
        scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
        scrollPane.getStyleClass().add("assets-details-contract-metadata-scroll");

        final VBox container = new VBox(8);
        container.getChildren().setAll(title, scrollPane);
        VBox.setVgrow(scrollPane, Priority.ALWAYS);
        return container;
    }

    private Node createCodecFieldRow(
            PackerCodecConfigurationFieldDTO field,
            AssetContractDraft draft,
            boolean editing) {
        final Node valueNode = switch (field.fieldType()) {
            case BOOLEAN -> createBooleanField(field, draft, editing);
            case ENUM -> createEnumField(field, draft, editing);
            case INTEGER, TEXT -> createTextField(field, draft, editing);
        };
        return AssetDetailsUiSupport.createKeyValueRow(field.label(), valueNode);
    }

    private Node createBooleanField(
            PackerCodecConfigurationFieldDTO field,
            AssetContractDraft draft,
            boolean editing) {
        final CheckBox checkBox = new CheckBox();
        checkBox.getStyleClass().add("assets-details-readonly-check");
        checkBox.setSelected(Boolean.parseBoolean(currentValue(field, draft)));
        if (editing) {
            checkBox.selectedProperty().addListener((ignored, oldValue, newValue) -> {
                if (!Objects.equals(oldValue, newValue)) {
                    formSession.updateDraft(current -> current.withCodecFieldValue(
                            current.selectedCodec(),
                            field.key(),
                            Boolean.toString(newValue)));
                    actionBar.updateState(formSession.mode(), formSession.isDirty());
                }
            });
        } else {
            configureReadOnlyControl(checkBox);
        }
        return checkBox;
    }

    private Node createEnumField(
            PackerCodecConfigurationFieldDTO field,
            AssetContractDraft draft,
            boolean editing) {
        final ComboBox<String> comboBox = new ComboBox<>(FXCollections.observableArrayList(field.options()));
        comboBox.getStyleClass().add("assets-details-combo");
        comboBox.setMaxWidth(Double.MAX_VALUE);
        comboBox.getSelectionModel().select(currentValue(field, draft));
        if (editing) {
            comboBox.valueProperty().addListener((ignored, oldValue, newValue) -> {
                if (newValue != null && !Objects.equals(oldValue, newValue)) {
                    formSession.updateDraft(current -> current.withCodecFieldValue(
                            current.selectedCodec(),
                            field.key(),
                            newValue));
                    actionBar.updateState(formSession.mode(), formSession.isDirty());
                }
            });
        } else {
            configureReadOnlyControl(comboBox);
        }
        return comboBox;
    }

    private Node createTextField(
            PackerCodecConfigurationFieldDTO field,
            AssetContractDraft draft,
            boolean editing) {
        final TextField textField = new TextField(currentValue(field, draft));
        textField.getStyleClass().add("assets-workspace-search");
        textField.setMaxWidth(Double.MAX_VALUE);
        HBox.setHgrow(textField, Priority.ALWAYS);
        if (editing) {
            textField.textProperty().addListener((ignored, oldValue, newValue) -> {
                if (!Objects.equals(oldValue, newValue)) {
                    formSession.updateDraft(current -> current.withCodecFieldValue(
                            current.selectedCodec(),
                            field.key(),
                            Objects.requireNonNullElse(newValue, "")));
                    actionBar.updateState(formSession.mode(), formSession.isDirty());
                }
            });
        } else {
            textField.setEditable(false);
            configureReadOnlyControl(textField);
        }
        return textField;
    }

    private String currentValue(PackerCodecConfigurationFieldDTO field, AssetContractDraft draft) {
        return draft.codecFieldValue(draft.selectedCodec(), field.key(), field.value());
    }

    private String currentMetadataValue(PackerCodecConfigurationFieldDTO field, AssetContractDraft draft) {
        return draft.metadataFieldValue(field.key(), field.value());
    }

    private void configureReadOnlyControl(javafx.scene.control.Control control) {
        control.setMouseTransparent(true);
        control.setFocusTraversable(false);
    }

    private void beginEdit() {
        if (formSession == null) {
            return;
        }
        formSession.beginEdit();
        render();
    }

    private void applyDraft() {
        if (formSession == null) {
            return;
        }
        final var draft = formSession.draft();
        Container.backgroundTasks().submit(() -> {
            final var request = new UpdateAssetContractRequest(
                    projectReference.toPackerProjectContext(),
                    draft.assetReference(),
                    draft.preload(),
                    draft.selectedCodec(),
                    draft.codecFieldValues(),
                    draft.metadataFieldValues());
            try {
                final var response = Container.packer().workspaceService().updateAssetContract(request);
                if (response.success()) {
                    Platform.runLater(() -> {
                        final var r = formSession.apply();
                        workspaceBus.publish(new StudioAssetLogEvent("ASSET DETAILS CONTRACT", "Asset contract updated"));
                        workspaceBus.publish(new StudioAssetsRefreshRequestedEvent(r.assetReference()));
                        render();
                    });
                } else {
                    Platform.runLater(() -> {
                        workspaceBus.publish(new StudioAssetLogEvent("ASSET DETAILS CONTRACT", response.errorMessage()));
                        cancelEdit();
                    });
                }
            } catch (Exception e) {
                workspaceBus.publish(new StudioAssetLogEvent("ASSET DETAILS CONTRACT", e.getMessage()));
                cancelEdit();
            }
        });
    }

    private void resetDraft() {
        if (formSession == null) {
            return;
        }
        formSession.resetDraft();
        render();
    }

    private void cancelEdit() {
        if (formSession == null) {
            return;
        }
        formSession.cancelEdit();
        render();
    }
}
