private record ParameterModel()

in core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ParameterNegotiationModel.java [340:571]


    private record ParameterModel(
            int paramIndex,
            @NonNull ObjectActionParameter metaModel,
            @NonNull ParameterNegotiationModel negotiationModel,
            @NonNull _BindableAbstract<ManagedObject> bindableParamValue,
            @NonNull BooleanBindable bindableParamValueDirtyFlag,
            @NonNull _BindableAbstract<String> bindableParamSearchArgument,
            @NonNull LazyObservable<String> observableParamValidation,
            @NonNull LazyObservable<Can<ManagedObject>> observableParamChoices,
            @NonNull LazyObservable<Consent> observableVisibilityConsent,
            @NonNull LazyObservable<Consent> observableUsabilityConsent,
            @NonNull Observable<String> observableParamAsTitle,
            @NonNull Observable<String> observableParamAsHtml,
            @NonNull _Lazy<Bindable<String>> bindableParamAsParsableTextLazy
        ) implements ManagedParameter {

        private ParameterModel(
            final int paramIndex,
            final @NonNull ParameterNegotiationModel negotiationModel,
            final @NonNull ManagedObject initialValue) {
            this(paramIndex, negotiationModel.getHead().getMetaModel().getParameterByIndex(paramIndex), negotiationModel,
                // bindableParamValue
                _Bindables.forValue(initialValue),
                // bindableParamValueDirtyFlag
                _Bindables.forBoolean(false),
                // bindableParamSearchArgument
                _Bindables.forValue(null),
                // unused in canonical constructor  ..
                null, null, null, null, null, null, null);
        }

        // canonical constructor
        ParameterModel(
            final int paramIndex,
            @NonNull final ObjectActionParameter metaModel,
            @NonNull final ParameterNegotiationModel negotiationModel,
            @NonNull final _BindableAbstract<ManagedObject> bindableParamValue,
            @NonNull final BooleanBindable bindableParamValueDirtyFlag,
            @NonNull final _BindableAbstract<String> bindableParamSearchArgument,
            // unused ..
            final LazyObservable<String> observableParamValidation,
            final LazyObservable<Can<ManagedObject>> observableParamChoices,
            final LazyObservable<Consent> observableVisibilityConsent,
            final LazyObservable<Consent> observableUsabilityConsent,
            final Observable<String> observableParamAsTitle,
            final Observable<String> observableParamAsHtml,
            final _Lazy<Bindable<String>> bindableParamAsParsableTextLazy
        ) {
            this.paramIndex = paramIndex;
            this.metaModel = metaModel;
            this.negotiationModel = negotiationModel;

            this.bindableParamValue = bindableParamValue;
            this.bindableParamValueDirtyFlag = bindableParamValueDirtyFlag;
            this.bindableParamSearchArgument = bindableParamSearchArgument;

            bindableParamValue().setValueGuard(MmAssertionUtils.assertInstanceOf(metaModel().getElementType()));
            bindableParamValue().addListener((event, oldValue, newValue)->{
                if(newValue==null) {
                    // lift null to empty ...
                    bindableParamValue().setValue(metaModel().getEmpty()); // triggers this event again
                    return;
                }
                negotiationModel().onNewParamValue();
                bindableParamValueDirtyFlag().setValue(true); // set dirty whenever an update event happens
            });

            // has either autoComplete, choices, or none
            this.observableParamChoices = metaModel().hasAutoComplete()
                ? _Observables.lazy(()->
                    metaModel().getAutoComplete(
                            negotiationModel(),
                            bindableParamSearchArgument().getValue(),
                            InteractionInitiatedBy.USER))
                : metaModel().hasChoices()
                    ? _Observables.lazy(()->
                        getMetaModel().getChoices(negotiationModel(), InteractionInitiatedBy.USER))
                    : _Observables.lazy(Can::empty);

            // if has autoComplete, then activate the search argument
            if(metaModel().hasAutoComplete()) {
                this.bindableParamSearchArgument.addListener((e,o,n)->{
                    observableParamChoices().invalidate();
                });
            }

            // validate this parameter, but only when validationFeedback has been activated
            this.observableParamValidation = _Observables.lazy(()->
                isValidationFeedbackActive()
                    ? negotiationModel().validateImmediately(paramIndex)
                    : (String)null);

            this.observableVisibilityConsent = _Observables.lazy(()->
                metaModel().isVisible(
                        negotiationModel().getHead(),
                        negotiationModel().getParamValues(),
                        InteractionInitiatedBy.USER));
            this.observableUsabilityConsent = _Observables.lazy(()->
                metaModel().isUsable(
                        negotiationModel().getHead(),
                        negotiationModel().getParamValues(),
                        InteractionInitiatedBy.USER));

            // value types should have associated rederers via value semantics
            this.observableParamAsTitle = _BindingUtil
                    .bindAsFormated(TargetFormat.TITLE, metaModel(), bindableParamValue());
            this.observableParamAsHtml = _BindingUtil
                    .bindAsFormated(TargetFormat.HTML, metaModel(), bindableParamValue());
            // value types should have associated parsers/formatters via value semantics
            // except for composite value types, which might have not
            this.bindableParamAsParsableTextLazy = _Lazy.threadSafe(()->(Bindable<String>) _BindingUtil
                    .bindAsFormated(TargetFormat.PARSABLE_TEXT, metaModel(), bindableParamValue()));
        }

        public void invalidateChoicesAndValidation() {
            observableParamChoices.invalidate();
            observableParamValidation.invalidate();
        }

        public void invalidateVisibilityAndUsability() {
            observableVisibilityConsent.invalidate();
            observableUsabilityConsent.invalidate();
        }

        private boolean isValidationFeedbackActive() {
            return negotiationModel().getObservableValidationFeedbackActive().getValue();
        }

        // -- MANAGED PARAMETER

        @Override
        public Identifier getIdentifier() {
            return getMetaModel().getFeatureIdentifier();
        }

        @Override
        public String getFriendlyName() {
            ObjectActionParameter objectActionParameter = getMetaModel();
            return objectActionParameter.getCanonicalFriendlyName();
        }

        @Override
        public Optional<String> getDescription() {
            return getMetaModel().getStaticDescription();
        }

        @Override
        public ObjectSpecification getElementType() {
            return getMetaModel().getElementType();
        }

        @Override
        public Bindable<ManagedObject> getValue() {
            return bindableParamValue;
        }

        @Override
        public Observable<String> getValueAsTitle() {
            return observableParamAsTitle;
        }

        @Override
        public Observable<String> getValueAsHtml() {
            return observableParamAsHtml;
        }

        @Override
        public boolean isValueAsParsableTextSupported() {
            return _BindingUtil.hasParser(metaModel);
        }

        @Override
        public Bindable<String> getValueAsParsableText() {
            return bindableParamAsParsableTextLazy.get();
        }

        @Override
        public Observable<String> getValidationMessage() {
            return observableParamValidation;
        }

        @Override
        public Bindable<String> getSearchArgument() {
            return bindableParamSearchArgument;
        }

        @Override
        public Observable<Can<ManagedObject>> getChoices() {
            return observableParamChoices;
        }

        @Override
        public Optional<InteractionVeto> checkUsability(@NonNull final Can<ManagedObject> params) {

            try {
                var head = negotiationModel().getHead();

                var usabilityConsent =
                    getMetaModel()
                    .isUsable(head, params, InteractionInitiatedBy.USER);

                return usabilityConsent.isVetoed()
                    ? Optional.of(InteractionVeto.readonly(usabilityConsent))
                    : Optional.empty();

            } catch (final Exception ex) {
                log.warn(ex.getLocalizedMessage(), ex);
                return Optional.of(InteractionVeto.readonly(new Veto("failure during usability evaluation")));
            }

        }

        // -- OBJECT CONTRACT

        @Override
        public final boolean equals(final Object obj) {
            return obj instanceof ParameterModel other
                ? Objects.equals(this.getIdentifier(), other.getIdentifier())
                : false;
        }

        @Override
        public final int hashCode() {
            return Objects.hashCode(getIdentifier());
        }

        @Override
        public final String toString() {
            return "ParameterModel[id=%s]".formatted(getIdentifier());
        }

    }