/*
 *  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.applib.services.metamodel;

import java.util.Optional;
import java.util.function.BiPredicate;

import jakarta.inject.Named;

import org.jspecify.annotations.Nullable;

import org.apache.causeway.applib.annotation.Action;
import org.apache.causeway.applib.annotation.DomainObject;
import org.apache.causeway.applib.annotation.DomainService;
import org.apache.causeway.applib.annotation.Property;
import org.apache.causeway.applib.id.LogicalType;
import org.apache.causeway.applib.services.bookmark.Bookmark;
import org.apache.causeway.applib.services.commanddto.processor.CommandDtoProcessor;
import org.apache.causeway.applib.services.metamodel.objgraph.ObjectGraph;
import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.schema.metamodel.v2.MetamodelDto;

import org.jspecify.annotations.NonNull;

/**
 * This service provides a formal API into the framework's metamodel.
 *
 * @apiNote This API is currently extremely limited, but the intention is to extend it
 * gradually as use cases emerge.
 *
 * @since 1.x {@index}
 */
public interface MetaModelService {

    /**
     * Provides a lookup by logicalTypeName of a domain class' object type, corresponding to
     * {@link Named#value()} or {@link DomainService#aliased()} or
     * {@link DomainObject#aliased()}.
     * Will return an empty result if there is no
     * such non-abstract class registered.
     * (interfaces and abstract types are never added to the lookup table).
     */
    Optional<LogicalType> lookupLogicalTypeByName(final String logicalTypeName);

    /**
     * Assuming that the {@link LogicalType} passed in actually represents a domain type, then
     * returns it along with any aliases defined as per
     * {@link DomainService#aliased()} or {@link DomainObject#aliased()}.
     *
     * <p>
     *     If there is no such domain type, then an empty {@link Can} will be returned.
     * </p>
     *
     * @param logicalType
     */
    Can<LogicalType> logicalTypeAndAliasesFor(final LogicalType logicalType);

    /**
     * Returns the {@link LogicalType} of a domain class' object type, corresponding to {@link Named#value()},
     * along with any aliases defined as per
     * {@link DomainService#aliased()} or {@link DomainObject#aliased()}.
     *
     * <p>
     *     If there is no such domain type, then an empty {@link Can} will be returned.
     * </p>
     */
    Can<LogicalType> logicalTypeAndAliasesFor(final String logicalTypeName);

    /**
     * Provides a lookup by class of a domain class' object type,corresponding to
     * {@link Named#value()} or {@link DomainService#aliased()} or
     * {@link DomainObject#aliased()}.
     */
    Optional<LogicalType> lookupLogicalTypeByClass(final Class<?> domainType);

    /**
     * Invalidates and rebuilds the internal metadata for the specified domain
     * type.
     */
    void rebuild(final Class<?> domainType);

    /**
     * Returns a list of representations of each of member of each domain class.
     *
     * <p>
     *     Used by {@link MetaModelServiceMenu} to return a downloadable CSV.
     * </p>
     *
     * <p>
     *     Note that {@link MetaModelService#exportMetaModel(Config)} provides a superset of
     *     the functionality provided by this method.
     * </p>
     *
     */
    DomainModel getDomainModel();

    /**
     * What sort of object the domain type represents.
     *
     * @param domainType
     * @param mode
     */
    BeanSort sortOf(@Nullable Class<?> domainType, Mode mode);

    /**
     * Override of {@link #sortOf(Class, Mode)}, extracting the domain type
     * from the provided {@link Bookmark} of a domain object instance.
     *
     * @param bookmark
     * @param mode
     */
    BeanSort sortOf(Bookmark bookmark, Mode mode);

    /**
     * Obtains the implementation of {@link CommandDtoProcessor} (if any) as
     * per {@link Action#commandDtoProcessor()} or
     * {@link Property#commandDtoProcessor()}.
     *
     * <p>
     *     This is used by framework-provided implementations of
     *     {@link org.apache.causeway.applib.services.conmap.ContentMappingService}.
     * </p>
     */
    CommandDtoProcessor commandDtoProcessorFor(
                            String logicalMemberIdentifier);

    /**
     * How {@link MetaModelService#sortOf(Class, Mode)} should act if an object
     * type is unknown.
     */
    enum Mode {
        /**
         * If the {@link #sortOf(Class, Mode) sort of} object type is unknown, then throw an exception.
         */
        STRICT,
        /**
         * If the {@link #sortOf(Class, Mode) sort of} object type is unknown, then return {@link BeanSort#UNKNOWN}.
         */
        RELAXED
    }

    /**
     * Exports the entire metamodel as a DTO, serializable into XML using JAXB.
     *
     * <p>
     *     The {@link Config} parameter can be used to restrict/filter the
     *     export to some subset of the metamodel; in particular to specific
     *     {@link Config#getNamespacePrefixes() namespace prefixes}.
     * </p>
     *
     * @param config - restricts/filters to a subsets of the metamodel.
     */
    MetamodelDto exportMetaModel(final Config config);

    /**
     * Can be used to create object relation diagrams (e.g. Plantuml).
     *
     * @param filter by {@link BeanSort} and {@link LogicalType} what to include in the resulting graph
     */
    ObjectGraph exportObjectGraph(final @NonNull BiPredicate<BeanSort, LogicalType> filter);

}
