/*
 *  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.codehaus.groovy.vmplugin.v16;

import groovy.lang.GroovyRuntimeException;
import org.apache.groovy.lang.annotation.Incubating;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.CompileUnit;
import org.codehaus.groovy.ast.RecordComponentNode;
import org.codehaus.groovy.vmplugin.v10.Java10;

import java.lang.invoke.MethodHandle;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.RecordComponent;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @deprecated Use {@link org.codehaus.groovy.vmplugin.v17.Java17} instead. Groovy 6.0 requires JDK 17+.
 */
@Deprecated(since = "6.0.0", forRemoval = true)
@SuppressWarnings("removal")
public class Java16 extends Java10 {

    /**
     * Returns the Java feature version handled by this plugin.
     *
     * @return {@code 16}
     */
    @Override
    public int getVersion() {
        return 16;
    }

    /**
     * Returns a handle for invoking the supplied method, including proxy default methods.
     *
     * @param method the method to invoke
     * @param receiver the bound receiver
     * @return a bound invocation handle or a wrapped failure
     */
    @Override
    public Object getInvokeSpecialHandle(final Method method, final Object receiver) {
        try {
            final Class<?> receiverClass = receiver.getClass();
            // GROOVY-10145, GROOVY-10391: default interface method proxy
            if (method.isDefault() && Proxy.isProxyClass(receiverClass)) {
                return new ProxyDefaultMethodHandle((Proxy) receiver, method);
            }
            var  lookup = newLookup(receiverClass);
            if (!lookup.hasFullPrivilegeAccess()) {
                return lookup.unreflect(method).bindTo(receiver);
            }
            return lookup.unreflectSpecial(method, receiverClass).bindTo(receiver);
        } catch (ReflectiveOperationException e) {
            return new GroovyRuntimeException(e);
        }
    }

    /**
     * Invokes a handle produced by {@link #getInvokeSpecialHandle(Method, Object)}.
     *
     * @param handle the handle or deferred failure to invoke
     * @param args the invocation arguments
     * @return the invocation result
     * @throws Throwable if invocation fails
     */
    @Override
    public Object invokeHandle(final Object handle, final Object[] args) throws Throwable {
        if (handle instanceof ProxyDefaultMethodHandle) {
            return ((ProxyDefaultMethodHandle) handle).invokeWithArguments(args);
        }
        if (handle instanceof Throwable) throw (Throwable) handle;
        MethodHandle mh = (MethodHandle) handle;
        return mh.invokeWithArguments(args);
    }

    /**
     * Adds record component metadata when the runtime class is a record.
     *
     * @param cu the owning compile unit
     * @param cn the class node to update
     * @param c the runtime class
     */
    @Override
    protected void makeRecordComponents(final CompileUnit cu, final ClassNode cn, final Class<?> c) {
        if (c.isRecord()) cn.setRecordComponents(Arrays.stream(c.getRecordComponents()).map(rc -> {
            ClassNode type = makeClassNode(cu, rc.getGenericType(), rc.getType());
            type = applyTypeAnnotations(rc.getAnnotatedType(), type);
            return new RecordComponentNode(cn, rc.getName(), type, Arrays.stream(rc.getAnnotations()).map(this::toAnnotationNode).collect(Collectors.toList()));
        }).collect(Collectors.toList()));
    }

    /**
     * Returns the component names declared by a record class.
     *
     * @param maybeRecord the class to inspect
     * @return the record component names, or the inherited fallback result
     */
    @Incubating
    @Override
    public List<String> getRecordComponentNames(Class<?> maybeRecord) {
        if (maybeRecord.isRecord()) {
            return Arrays.stream(maybeRecord.getRecordComponents()).map(RecordComponent::getName).toList();
        }
        return super.getRecordComponentNames(maybeRecord);
    }
}
