content: /*
   *  Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you under the Apache License, Version 2.0 (the
   *  "License"); you may not use this file except in compliance
   *  with the License.  You may obtain a copy of the License at
   *
   *        http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing,
   *  software distributed under the License is distributed on an
   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   *  KIND, either express or implied.  See the License for the
   *  specific language governing permissions and limitations
   *  under the License.
   */
  package org.apache.causeway.core.metamodel.object;
  
  import java.util.List;
  import java.util.Optional;
  
  import org.jspecify.annotations.NonNull;
  import org.jspecify.annotations.Nullable;
  
  import org.apache.causeway.applib.fa.FontAwesomeLayers;
  import org.apache.causeway.applib.services.bookmark.Bookmark;
  import org.apache.causeway.applib.services.repository.EntityState;
  import org.apache.causeway.commons.collections.Can;
  import org.apache.causeway.commons.functional.Either;
  import org.apache.causeway.commons.internal.assertions._Assert;
  import org.apache.causeway.commons.internal.exceptions._Exceptions;
  import org.apache.causeway.core.metamodel.context.HasMetaModelContext;
  import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIcon;
  import org.apache.causeway.core.metamodel.spec.HasObjectSpecification;
  import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
  import org.apache.causeway.core.metamodel.spec.feature.ObjectActionParameter;
  import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation;
  import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
  
  import lombok.Getter;
  import lombok.RequiredArgsConstructor;
  import lombok.extern.log4j.Log4j2;
  
  /**
   * Represents an instance of some element of the meta-model recognized by the framework,
   * that is <i>Spring</i> managed beans, persistence-stack provided entities, view-models,
   * mixins or instances of value types.
   *
   * @since 2.0 {@index}}
   *
   */
  public sealed interface ManagedObject
  extends
      Bookmarkable,
      HasMetaModelContext,
      HasObjectSpecification
  permits
      ManagedObjectEmpty,
      ManagedObjectUnspecified,
      ManagedObjectMixin,
      ManagedObjectOther,
      ManagedObjectValue,
      ManagedObjectService,
      ManagedObjectViewmodel,
      ManagedObjectEntity,
      PackedManagedObject {
  
      /**
       * ManagedObject specializations have varying contract/behavior.
       */
      @Getter
      @RequiredArgsConstructor
      @Log4j2
      enum Specialization {
          /**
           * <h1>Contract</h1><ul>
           * <li>Specification (null, immutable)</li>
           * <li>Bookmark (n/a)</li>
           * <li>Pojo (null, immutable)</li>
           * </ul>
           * @implNote realized by a singleton (static) {@link ManagedObject} instance;
           */
          UNSPECIFIED(TypePolicy.NO_TYPE, BookmarkPolicy.NO_BOOKMARK, PojoPolicy.NO_POJO,
                  InjectionPolicy.NEVER_INJECT),
  
          /**
           * <h1>Contract</h1><ul>
           * <li>Specification (immutable, allowed to correspond to abstract type)</li>
           * <li>Bookmark (n/a)</li>
           * <li>Pojo (null, immutable)</li>
           * </ul>
           */
          EMPTY(TypePolicy.ABSTRACT_TYPE_ALLOWED, BookmarkPolicy.NO_BOOKMARK, PojoPolicy.NO_POJO,
                  InjectionPolicy.NEVER_INJECT),
  
          /**
           * <h1>Contract</h1><ul>
           * <li>Specification (immutable, NOT allowed to correspond to abstract type)</li>
           * <li>Bookmark (immutable)</li>
           * <li>Pojo (immutable)</li>
           * </ul>
           */
          VALUE(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.IMMUTABLE, PojoPolicy.IMMUTABLE,
                  InjectionPolicy.NEVER_INJECT),
  
          /**
           * <h1>Contract</h1><ul>
           * <li>Specification (immutable, NOT allowed to correspond to abstract type)</li>
           * <li>Bookmark (immutable)</li>
           * <li>Pojo (immutable)</li>
           * </ul>
           */
          SERVICE(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.IMMUTABLE, PojoPolicy.IMMUTABLE,
                  InjectionPolicy.NEVER_INJECT),
  
          /**
           * <h1>Contract</h1><ul>
           * <li>Specification (immutable, NOT allowed to correspond to abstract type)</li>
           * <li>Bookmark (refreshable, as VM state changes manifest in change of ID)</li>
           * <li>Pojo (mutable, but immutable obj. ref.)</li>
           * </ul>
           */
          VIEWMODEL(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.REFRESHABLE, PojoPolicy.STATEFUL,
                  InjectionPolicy.ALWAYS_INJECT),
  
          /**
           * <h1>Contract</h1><ul>
           * <li>Specification (immutable, NOT allowed to correspond to abstract type)</li>
           * <li>Bookmark (immutable,  entity must be persistent, it must have an ID,  fail otherwise)</li>
           * <li>Pojo (refetchable)</li>
           * </ul>
           */
          ENTITY(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.IMMUTABLE, PojoPolicy.REFETCHABLE,
                  InjectionPolicy.ALWAYS_INJECT),
  
          /**
           * <h1>Contract</h1><ul>
           * <li>Element Specification (immutable, NOT allowed to correspond to abstract type)</li>
           * <li>Bookmark (n/a)</li>
           * <li>Pojo (allowed stateful, immutable obj. ref)</li>
           * </ul>
           */
          MIXIN(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.NO_BOOKMARK, PojoPolicy.STATEFUL,
                  InjectionPolicy.ALWAYS_INJECT),
  
          /**
           * <h1>Contract</h1><ul>
           * <li>Element Specification (immutable, NOT allowed to correspond to abstract type)</li>
           * <li>Bookmark (n/a)</li>
           * <li>Pojo (allowed stateful, immutable obj. ref)</li>
           * </ul>
           */
          OTHER(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.NO_BOOKMARK, PojoPolicy.STATEFUL,
                  InjectionPolicy.NEVER_INJECT),
  
          /**
           * <h1>Contract</h1><ul>
           * <li>Element Specification (immutable, NOT allowed to correspond to abstract type)</li>
           * <li>Bookmark (n/a)</li>
           * <li>Pojo (unmod. Collection of pojos)</li>
           * </ul>
           */
          PACKED(TypePolicy.ABSTRACT_TYPE_ALLOWED, BookmarkPolicy.NO_BOOKMARK, PojoPolicy.PACKED,
                  InjectionPolicy.NEVER_INJECT);
  
          static enum TypePolicy {
              /** has no type information */
              NO_TYPE,
              /** has type information, abstract types are allowed */
              ABSTRACT_TYPE_ALLOWED,
              /** has type information, exact types are required */
              EXACT_TYPE_REQUIRED;
              ////
              /** has no type information */
              public boolean isNoType() { return this == NO_TYPE; }
              /** has type information, abstract types are allowed */
              public boolean isAbstractTypeAllowed() { return this == ABSTRACT_TYPE_ALLOWED; }
              /** has type information, exact types are required */
              public boolean isExactTypeRequired() { return this == EXACT_TYPE_REQUIRED; }
              /** has type information */
              public boolean isTypeRequiredAny() { return !isNoType(); }
          }
          static enum BookmarkPolicy {
              /** has no {@link Bookmark} */
              NO_BOOKMARK,
              /** has an immutable {@link Bookmark} */
              IMMUTABLE,
              /** has an refreshable {@link Bookmark}, that is a mutable object reference */
              REFRESHABLE;
              ////
              /** has no {@link Bookmark} */
              public boolean isNoBookmark() { return this == NO_BOOKMARK; }
              /** has an immutable {@link Bookmark} */
              public boolean isImmutable() { return this == IMMUTABLE; }
              /** has an refreshable {@link Bookmark}, that is a mutable object reference */
              public boolean isRefreshable() { return this == REFRESHABLE; }
          }
          static enum PojoPolicy {
              /** has no pojo, immutable <code>null</code> */
              NO_POJO,
              /** has a non-null pojo, immutable, with immutable object reference */
              IMMUTABLE,
              /** has a stateful pojo, with immutable object reference */
              STATEFUL,
              /** has a stateful pojo, with mutable object reference */
              REFETCHABLE,
              /** creates an unmodifiable collection of pojos (lazily);
               * supports unpacking into a {@link Can} of {@link ManagedObject}s;*/
              PACKED;
              ////
              /** has no pojo, immutable <code>null</code> */
              public boolean isNoPojo() { return this == NO_POJO; }
              /** has a non-null pojo, immutable, with immutable object reference */
              public boolean isImmutable() { return this == IMMUTABLE; }
              /** has a stateful pojo, with immutable object reference */
              public boolean isStateful() { return this == STATEFUL; }
              /** has a stateful pojo, with mutable object reference */
              public boolean isRefetchable() { return this == REFETCHABLE; }
              /** creates an unmodifiable collection of pojos (lazily);
               * supports unpacking into a {@link Can} of {@link ManagedObject}s;*/
              public boolean isPacked() { return this == PACKED; }
          }
          static enum InjectionPolicy {
              /** don't inject services into accompanied pojo */
              NEVER_INJECT,
              /** always inject services into accompanied pojo */
              ALWAYS_INJECT;
              ////
              /** don't inject services into accompanied pojo */
              public boolean isNeverInject() { return this == NEVER_INJECT; }
              /** always inject services into accompanied pojo */
              public boolean isAlwaysInject() { return this == ALWAYS_INJECT; }
          }
  
          private final TypePolicy typePolicy;
          private final BookmarkPolicy bookmarkPolicy;
          private final PojoPolicy pojoPolicy;
          private final InjectionPolicy injectionPolicy;
  
          /**
           * UNSPECIFIED
           * @see TypePolicy#NO_TYPE
           * @see BookmarkPolicy#NO_BOOKMARK
           * @see PojoPolicy#NO_POJO
           */
          public boolean isUnspecified() { return this == UNSPECIFIED; }
          public boolean isSpecified() { return this != UNSPECIFIED; }
          /**
           * EMPTY
           * @see TypePolicy#ABSTRACT_TYPE_ALLOWED
           * @see BookmarkPolicy#NO_BOOKMARK
           * @see PojoPolicy#NO_POJO
           */
          public boolean isEmpty() { return this == EMPTY; }
          /**
           * VALUE
           * @see TypePolicy#EXACT_TYPE_REQUIRED
           * @see BookmarkPolicy#IMMUTABLE
           * @see PojoPolicy#IMMUTABLE
           */
          public boolean isValue() { return this == VALUE; }
          /**
           * SERVICE
           * @see TypePolicy#EXACT_TYPE_REQUIRED
           * @see BookmarkPolicy#IMMUTABLE
           * @see PojoPolicy#IMMUTABLE
           */
          public boolean isService() { return this == SERVICE; }
          /**
           * VIEWMODEL
           * @see TypePolicy#EXACT_TYPE_REQUIRED
           * @see BookmarkPolicy#REFRESHABLE
           * @see PojoPolicy#STATEFUL
           */
          public boolean isViewmodel() { return this == VIEWMODEL; }
          /**
           * ENTITY
           * @see TypePolicy#EXACT_TYPE_REQUIRED
           * @see BookmarkPolicy#IMMUTABLE
           * @see PojoPolicy#REFETCHABLE
           */
          public boolean isEntity() { return this == ENTITY; }
          /**
           * MIXIN
           * @see TypePolicy#EXACT_TYPE_REQUIRED
           * @see BookmarkPolicy#NO_BOOKMARK
           * @see PojoPolicy#STATEFUL
           */
          public boolean isMixin() { return this == MIXIN; }
          /**
           * OTHER
           * @see TypePolicy#EXACT_TYPE_REQUIRED
           * @see BookmarkPolicy#NO_BOOKMARK
           * @see PojoPolicy#STATEFUL
           */
          public boolean isOther() { return this == OTHER; }
          /**
           * PACKED
           * @see TypePolicy#ABSTRACT_TYPE_ALLOWED
           * @see BookmarkPolicy#NO_BOOKMARK
           * @see PojoPolicy#PACKED
           */
          public boolean isPacked() { return this == PACKED; }
  
          public static Specialization inferFrom(
                  final @Nullable ObjectSpecification spec,
                  final @Nullable Object pojo) {
              if(spec==null) { return UNSPECIFIED; }
              if(spec.isPlural()) { return PACKED; }
              if(pojo==null) { return EMPTY; }
              if(spec.isValue()) { return VALUE; }
              if(spec.isInjectable()) { return SERVICE; }
              if(spec.isViewModel()) { return VIEWMODEL; }
              if(spec.isEntity()) { return ENTITY; }
              if(spec.isMixin()) { return MIXIN; }
              if(!spec.isAbstract()) { return OTHER; }
              log.warn("failed specialization attempt for {}", spec);
              return UNSPECIFIED;
          }
  
          <T> T assertCompliance(
                  final ObjectSpecification objSpec,
                  final @NonNull T pojo) {
              final Specialization specialization = this;
              MmAssertionUtils.assertPojoNotWrapped(pojo);
              if(objSpec.isAbstract()) {
                  _Assert.assertFalse(specialization.getTypePolicy().isExactTypeRequired(),
                          ()->String.format("Specialization %s does not allow abstract type %s",
                                  specialization,
                                  objSpec));
              }
              if(specialization.getTypePolicy().isExactTypeRequired()) {
                  MmAssertionUtils.assertExactType(objSpec, pojo);
              }
              if(specialization.getInjectionPolicy().isAlwaysInject()) {
                  var isInjectionPointsResolved = objSpec.entityFacet()
                      .map(entityFacet->entityFacet.isInjectionPointsResolved(pojo))
                      .orElse(false);
                  if(!isInjectionPointsResolved) {
                      objSpec.getServiceInjector().injectServicesInto(pojo); // might be redundant
                  }
              }
              return pojo;
          }
      }
  
      /**
       * Returns the specific {@link Specialization} this {@link ManagedObject} implements,
       * which governs this object's behavior.
       */
      Specialization specialization();
  
      @Deprecated default Specialization getSpecialization() { return specialization(); }
  
      @Override default Specialization.BookmarkPolicy getBookmarkPolicy() {
          return specialization().getBookmarkPolicy();
      }
  
      /**
       * Returns the specification that details the structure (meta-model) of this object.
       */
      @Override ObjectSpecification objSpec();
  
      /**
       * Returns the adapted domain object, the 'plain old java' object this managed object
       * represents with the framework.
       */
      Object getPojo();
  
      @NonNull
      default EntityState getEntityState() {
          return EntityState.NOT_PERSISTABLE;
      }
  
      // -- TITLE
  
      /**
       * The (untranslated) title of the wrapped pojo.
       */
      String getTitle();
  
      // -- SHORTCUT - ICON
  
      /**
       * Returns the name of an icon to use for this object.
       * <p>
       * May return <code>null</code> if no icon is specified.
       */
      default String getIconName() {
          return objSpec().getIconName(this);
      }
  
      default ObjectIcon getIcon() {
          return objSpec().getIcon(this);
      }
  
      /**
       * Domain Objects may either have an icon corresponding to an icon resource,
       * or they use a font awesome icon.
       */
      default Either<ObjectIcon, FontAwesomeLayers> eitherIconOrFaLayers() {
          var iconName = getIconName();
          var faLayers = objSpec().getFaLayers(this).orElse(null);
          if (iconName != null
                  || faLayers == null) {
              return Either.left(getIcon());
          } else {
              return Either.right(faLayers);
          }
      }
  
      default Either<ManagedObject, ManagedObject> asEitherWithOrWithoutMemoizedBookmark() {
          return isBookmarkMemoized()
              ? Either.left(this)
              : Either.right(this);
      }
  
      // -- FACTORIES
  
      /**
       * Factory for Specialization#UNSPECIFIED.
       * @see ManagedObject.Specialization.TypePolicy#NO_TYPE
       * @see ManagedObject.Specialization.BookmarkPolicy#NO_BOOKMARK
       * @see ManagedObject.Specialization.PojoPolicy#NO_POJO
       */
      static ManagedObject unspecified() {
          return ManagedObjectUnspecified.INSTANCE;
      }
      /**
       * EMPTY
       * @param spec - required
       * @see ManagedObject.Specialization.TypePolicy#ABSTRACT_TYPE_ALLOWED
       * @see ManagedObject.Specialization.BookmarkPolicy#NO_BOOKMARK
       * @see ManagedObject.Specialization.PojoPolicy#NO_POJO
       */
      static ManagedObject empty(final @NonNull ObjectSpecification spec) {
          return new ManagedObjectEmpty(spec);
      }
      /**
       * VALUE
       * @param spec - required
       * @param pojo - if <code>null</code> maps to {@link #empty(ObjectSpecification)}
       * @see ManagedObject.Specialization.TypePolicy#EXACT_TYPE_REQUIRED
       * @see ManagedObject.Specialization.BookmarkPolicy#IMMUTABLE
       * @see ManagedObject.Specialization.PojoPolicy#IMMUTABLE
       */
      static ManagedObject value(
              final @NonNull ObjectSpecification spec,
              final @Nullable Object pojo) {
          return pojo != null
                  ? new ManagedObjectValue(spec, pojo)
                  : empty(spec);
      }
      /**
       * SERVICE
       * @param spec - required
       * @param pojo - required
       * @see ManagedObject.Specialization.TypePolicy#EXACT_TYPE_REQUIRED
       * @see ManagedObject.Specialization.BookmarkPolicy#IMMUTABLE
       * @see ManagedObject.Specialization.PojoPolicy#IMMUTABLE
       */
      static ManagedObject service(
              final @NonNull ObjectSpecification spec,
              final @NonNull Object pojo) {
          return new ManagedObjectService(spec, pojo);
      }
      /**
       * VIEWMODEL
       * @param spec - required
       * @param pojo - if <code>null</code> maps to {@link #empty(ObjectSpecification)}
       * @see ManagedObject.Specialization.TypePolicy#EXACT_TYPE_REQUIRED
       * @see ManagedObject.Specialization.BookmarkPolicy#REFRESHABLE
       * @see ManagedObject.Specialization.PojoPolicy#STATEFUL
       */
      static ManagedObject viewmodel(
              final @NonNull ObjectSpecification spec,
              final @Nullable Object pojo,
              final Optional<Bookmark> bookmarkIfKnown) {
          return pojo != null
                  ? new ManagedObjectViewmodel(spec, pojo, bookmarkIfKnown)
                  : empty(spec);
      }
      /**
       * ENTITY
       * @param spec - required
       * @param pojo - if <code>null</code> maps to {@link #empty(ObjectSpecification)}
       * @param bookmark
       * @see ManagedObject.Specialization.TypePolicy#EXACT_TYPE_REQUIRED
       * @see ManagedObject.Specialization.BookmarkPolicy#IMMUTABLE
       * @see ManagedObject.Specialization.PojoPolicy#REFETCHABLE
       */
      static ManagedObject entity(
              final @NonNull ObjectSpecification spec,
              final @Nullable Object pojo,
              final @NonNull Optional<Bookmark> bookmarkIfKnown) {
          if(pojo == null) return empty(spec);
  
          var bookmarkIfAny = bookmarkIfKnown
                  .or(()->spec.entityFacetElseFail().bookmarkFor(pojo));
          return bookmarkIfAny
              .map(bookmark->entityBookmarked(spec, pojo, bookmark))
              .orElseGet(()->entityTransient(spec, pojo));
      }
      private static ManagedObject entityBookmarked(
              final @NonNull ObjectSpecification spec,
              final @NonNull Object pojo,
              final @NonNull Bookmark bookmark) {
          return new ManagedObjectEntity(
                          new EntityPhaseBookmarked(spec, pojo, bookmark));
      }
      private static ManagedObject entityTransient(
              final @NonNull ObjectSpecification spec,
              final @Nullable Object pojo) {
          return pojo != null
                  ? new ManagedObjectEntity(
                          new EntityPhaseTransient(spec, pojo))
                  : empty(spec);
      }
      /**
       * MIXIN
       * @param spec - required
       * @param pojo - required
       * @see ManagedObject.Specialization.TypePolicy#EXACT_TYPE_REQUIRED
       * @see ManagedObject.Specialization.BookmarkPolicy#NO_BOOKMARK
       * @see ManagedObject.Specialization.PojoPolicy#STATEFUL
       */
      static ManagedObject mixin(
              final @NonNull ObjectSpecification spec,
              final @NonNull Object pojo) {
          return new ManagedObjectMixin(spec, pojo);
      }
      /**
       * OTHER
       * @apiNote potential use case: allows to work with detached entity pojos
       *      in a light weight fashion,
       *      treating them as data snapshots, not considering state changes
       *      (e.g. reuse of ORM metadata while accessing a secondary data store)
       * @param spec - required
       * @param pojo - if <code>null</code> maps to {@link #empty(ObjectSpecification)}
       * @see ManagedObject.Specialization.TypePolicy#EXACT_TYPE_REQUIRED
       * @see ManagedObject.Specialization.BookmarkPolicy#NO_BOOKMARK
       * @see ManagedObject.Specialization.PojoPolicy#STATEFUL
       */
      public static ManagedObject other(
              final @NonNull ObjectSpecification spec,
              final @Nullable Object pojo) {
          return pojo != null
                  ? new ManagedObjectOther(spec, pojo)
                  : empty(spec);
      }
      /**
       * PACKED
       * @param elementSpec - required
       * @param nonScalar - if <code>null</code> uses {@link Can#empty()} instead
       * @see ManagedObject.Specialization.TypePolicy#ABSTRACT_TYPE_ALLOWED
       * @see ManagedObject.Specialization.BookmarkPolicy#NO_BOOKMARK
       * @see ManagedObject.Specialization.PojoPolicy#PACKED
       */
      static PackedManagedObject packed(
              final @NonNull ObjectSpecification elementSpec,
              final @Nullable Can<ManagedObject> nonScalar) {
          return new PackedManagedObject(elementSpec, nonScalar);
      }
  
      /**
       * For cases, when the pojo's specification is not available and needs to be looked up.
       * <p>
       * Fails if the pojo is not a singular (eg. collection).
       * @param specLoader - required
       * @param pojo - required, required non-scalar
       */
      static ManagedObject adaptSingular(
              final @NonNull SpecificationLoader specLoader,
              final @NonNull Object pojo) {
          var spec = specLoader.specForType(pojo.getClass()).orElse(null);
          return adaptSingularInternal(spec, pojo, Optional.empty());
      }
  
      static ManagedObject adaptSingular(
              final @NonNull ObjectSpecification guess,
              final @Nullable Object pojo) {
          return adaptSingularInternal(guess, pojo, Optional.empty());
      }
  
      static ManagedObject adaptProperty(
              final @NonNull OneToOneAssociation oneToOneAssociation,
              final @Nullable Object pojo) {
          return adaptSingularInternal(oneToOneAssociation.getElementType(), pojo, Optional.empty());
      }
  
      static ManagedObject adaptParameter(
              final @NonNull ObjectActionParameter param,
              final @Nullable Object paramValue) {
  
          return param.isSingular()
                  ? adaptSingular(param.getElementType(), paramValue)
                  // else adopt each element pojo then pack
                  : packed(param.getElementType(),
                  ManagedObjects.adaptMultipleOfType(param.getElementType(), paramValue));
      }
  
      static Can<ManagedObject> adaptParameters(
              final Can<ObjectActionParameter> objectActionParameters,
              final List<Object> args) {
          return objectActionParameters.zipMap(args, ManagedObject::adaptParameter);
      }
  
      /**
       * Optimized for cases, when the pojo's specification and bookmark are already available.
       */
      static ManagedObject bookmarked(
              final @NonNull  ObjectSpecification spec,
              final @Nullable Object pojo,
              final @NonNull  Bookmark bookmark) {
          return adaptSingularInternal(spec, pojo, Optional.of(bookmark));
      }
  
      // -- HELPER
  
      /**
       * spec and pojo don't need to be strictly in sync, we adapt if required
       */
      private static ManagedObject adaptSingularInternal(
              final @Nullable ObjectSpecification guess,
              final @Nullable Object pojo,
              final @NonNull Optional<Bookmark> bookmarkIfAny) {
  
          MmAssertionUtils.assertPojoNotWrapped(pojo);
          MmAssertionUtils.assertPojoIsScalar(pojo);
          var spec = pojo!=null
                  && guess!=null
                      ? MmSpecUtils.quicklyResolveObjectSpecification(guess, pojo.getClass())
                      : guess;
  
          var specialization = spec!=null
                  ? Specialization.inferFrom(spec, pojo)
                  : Specialization.UNSPECIFIED;
  
          switch(specialization) {
          case UNSPECIFIED:
              return unspecified();
          case EMPTY:
              return empty(spec);
          case VALUE:
              return value(spec, pojo);
          case SERVICE:
              return service(spec, pojo);
          case VIEWMODEL:
              return viewmodel(spec, pojo, bookmarkIfAny);
          case ENTITY:
              return entity(spec, pojo, bookmarkIfAny);
          case MIXIN:
              return mixin(spec, pojo);
          case OTHER:
              return other(spec, pojo);
          case PACKED:
              throw _Exceptions.unexpectedCodeReach();
          }
          throw _Exceptions.unmatchedCase(specialization);
      }
  
  }
  