/*
 *  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.runtime.wrap;

import java.lang.reflect.Modifier;
import java.util.List;

import org.jspecify.annotations.Nullable;

import org.apache.causeway.applib.services.wrapper.WrapperFactory;
import org.apache.causeway.applib.services.wrapper.control.SyncControl;
import org.apache.causeway.commons.internal.proxy.ProxyFactoryService.AdditionalField;
import org.apache.causeway.commons.internal.reflection._Reflect;
import org.apache.causeway.core.metamodel.object.ManagedObject;

import lombok.SneakyThrows;

/**
 * Implemented by all objects that have been viewed as per
 * {@link WrapperFactory#wrap(Object)}.
 *
 * @implNote domain classes may not have methods with <tt>__causeway_</tt> prefix
 *
 * @apiNote requires the mechanism that creates proxies implementing {@link WrappingObject}
 *      to additionally create the {@code __causeway_origin_field}.
 *
 * @since 1.x, revised for 3.4 {@index}
 */
public interface WrappingObject {

    final static String ORIGIN_GETTER_NAME = "__causeway_origin";
    final static String ORIGIN_FIELD_NAME = "__causeway_origin_field";
    final static String SAVE_METHOD_NAME = "__causeway_save";

    final static List<AdditionalField> ADDITIONAL_FIELDS = List.of(
            new AdditionalField(ORIGIN_FIELD_NAME, WrappingObject.Origin.class, Modifier.PROTECTED));

    record Origin(
            Object pojo,
            /**
             * The mixee adapted as a ManagedObject, used only if pojo is a mixin.
             */
            @Nullable ManagedObject managedMixee,
            SyncControl syncControl,
            boolean isFallback) {
        /**
         * fallback, used for non-proxied target, with no execute (no verify no rule checking).
         */
        public static Origin fallback(Object target) {
            return new Origin(target, null, SyncControl.defaults().withNoExecute(), true);
        }
        /**
         * fallback, used for non-proxied target as mixin, with no execute (no verify no rule checking)
         */
        public static Origin fallbackMixin(Object target, ManagedObject managedMixee) {
            return new Origin(target, managedMixee, SyncControl.defaults().withNoExecute(), true);
        }
        public Origin(Object pojo, SyncControl syncControl) {
            this(pojo, null, syncControl, false);
        }
        public Origin(Object pojo, ManagedObject managedMixee, SyncControl syncControl) {
            this(pojo, managedMixee, syncControl, false);
        }
    }

    /**
     * Getter for the underlying {@link Origin}.
     */
    Origin __causeway_origin();

    /**
     * Getter for the underlying {@link Origin}.
     */
    @SneakyThrows
    static Origin getOrigin(WrappingObject proxyObject)  {
        var field = proxyObject.getClass().getDeclaredField(ORIGIN_FIELD_NAME);
        return (Origin) _Reflect.getFieldOn(field, proxyObject);
    }

    /**
     * Wither for the underlying {@link Origin}.
     */
    @SneakyThrows
    static <T> T withOrigin(T proxyObject, Origin origin) {
        var field = proxyObject.getClass().getDeclaredField(ORIGIN_FIELD_NAME);
        _Reflect.setFieldOn(field, proxyObject, origin);
        return proxyObject;
    }

    /**
     * Programmatic equivalent of invoking save for a transient object .
     */
    void __causeway_save();

}
