/*
 * Copyright 2015-2026 the original author or authors.
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License v2.0 which
 * accompanies this distribution and is available at
 *
 * https://www.eclipse.org/legal/epl-v20.html
 */

package org.junit.platform.commons.util;

import static org.apiguardian.api.API.Status.INTERNAL;
import static org.junit.platform.commons.util.ReflectionUtils.EMPTY_CLASS_ARRAY;
import static org.junit.platform.commons.util.ReflectionUtils.findMethod;
import static org.junit.platform.commons.util.ReflectionUtils.isStatic;
import static org.junit.platform.commons.util.ReflectionUtils.tryToLoadClass;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;

import org.apiguardian.api.API;
import org.junit.platform.commons.function.Try;

/**
 * Internal Kotlin-specific reflection utilities
 *
 * @since 1.13.3
 */
@API(status = INTERNAL, since = "1.13.3")
public class KotlinReflectionUtils {

	private static final String DEFAULT_IMPLS_CLASS_NAME = "DefaultImpls";

	private static final Class<? extends Annotation> kotlinMetadata;

	static {
		Try<Class<? extends Annotation>> metadata = tryToLoadKotlinMetadataClass();
		kotlinMetadata = metadata.toOptional().orElse(null);
	}

	@SuppressWarnings("unchecked")
	private static Try<Class<? extends Annotation>> tryToLoadKotlinMetadataClass() {
		return tryToLoadClass("kotlin.Metadata") //
				.andThenTry(it -> (Class<? extends Annotation>) it);
	}

	/**
	 * Determines whether the supplied class is a {@code DefaultImpls} class
	 * generated by the Kotlin compiler.
	 *
	 * <p>See
	 * <a href="https://kotlinlang.org/docs/interfaces.html#jvm-default-method-generation-for-interface-functions">Kotlin documentation</a>
	 * for details.
	 */
	public static boolean isKotlinInterfaceDefaultImplsClass(Class<?> clazz) {
		if (!isKotlinType(clazz) || !DEFAULT_IMPLS_CLASS_NAME.equals(clazz.getSimpleName()) || !isStatic(clazz)) {
			return false;
		}

		Class<?> enclosingClass = clazz.getEnclosingClass();
		if (enclosingClass != null && enclosingClass.isInterface()) {
			return Arrays.stream(clazz.getDeclaredMethods()) //
					.anyMatch(method -> isCompilerGeneratedDefaultMethod(method, enclosingClass));
		}

		return false;
	}

	private static boolean isCompilerGeneratedDefaultMethod(Method method, Class<?> enclosingClass) {
		if (isStatic(method) && method.getParameterCount() > 0) {
			Class<?>[] parameterTypes = method.getParameterTypes();
			if (parameterTypes[0] == enclosingClass) {
				Class<?>[] originalParameterTypes = copyWithoutFirst(parameterTypes);
				return findMethod(enclosingClass, method.getName(), originalParameterTypes).isPresent();
			}
		}
		return false;
	}

	private static Class<?>[] copyWithoutFirst(Class<?>[] values) {
		if (values.length == 1) {
			return EMPTY_CLASS_ARRAY;
		}
		Class<?>[] result = new Class<?>[values.length - 1];
		System.arraycopy(values, 1, result, 0, result.length);
		return result;
	}

	private static boolean isKotlinType(Class<?> clazz) {
		return kotlinMetadata != null //
				&& clazz.getDeclaredAnnotation(kotlinMetadata) != null;
	}

}
