package kotlinx.schema.generator.reflect

import kotlinx.schema.generator.core.ir.Introspections
import kotlinx.schema.generator.core.ir.TypeRef
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1
import kotlin.reflect.KType
import kotlin.reflect.full.allSuperclasses
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.javaMethod
import kotlin.reflect.jvm.kotlinFunction

/**
 * Gets the [KType.classifier], ensuring it is a [KClass].
 */
internal val KType.klass: KClass<*> get() {
    return requireNotNull(classifier as? KClass<*>) {
        "Unsupported classifier: $classifier. " +
            "Only KClass classifiers are supported. Type parameters and other classifiers " +
            "cannot be introspected using reflection."
    }
}

/**
 * Finds a property in a class by name.
 * Used when extracting metadata from constructor parameters to find corresponding property annotations.
 *
 * @param klass The class to search in
 * @param propertyName The name of the property to find
 * @return The property if found, null otherwise
 */
internal fun findPropertyByName(
    klass: KClass<*>,
    propertyName: String,
): KProperty<*>? =
    klass.members
        .filterIsInstance<KProperty<*>>()
        .firstOrNull { it.name == propertyName }

/**
 * Extracts description from annotations.
 *
 * @see [Introspections.getDescriptionFromAnnotation]
 */
internal fun extractDescription(annotations: List<Annotation>): String? =
    annotations.firstNotNullOfOrNull { annotation ->
        val annotationName = annotation.annotationClass.simpleName ?: return@firstNotNullOfOrNull null
        val annotationArguments =
            buildList {
                annotation.annotationClass.members
                    .filterIsInstance<KProperty1<Annotation, *>>()
                    .forEach { property ->
                        add(property.name to property.get(annotation))
                    }
            }
        Introspections.getDescriptionFromAnnotation(annotationName, annotationArguments)
    }

/**
 * Returns a new [TypeRef] with the specified nullable flag.
 */
internal fun TypeRef.withNullable(nullable: Boolean): TypeRef =
    when (this) {
        is TypeRef.Inline -> copy(nullable = nullable)
        is TypeRef.Ref -> copy(nullable = nullable)
    }

/**
 * Finds effective primary constructor of a class, filtering synthetic constructors generated by kotlinx.serialization.
 */
internal fun findPrimaryConstructor(klass: KClass<*>): KFunction<Any>? {
    /**
     * Check if this is a kotlinx.serialization generated synthetic constructor
     * with a SerializationConstructorMarker param.
     */
    fun KFunction<Any>.isSyntheticSerializationConstructor(): Boolean =
        parameters.any { param ->
            (param.type.classifier as? KClass<*>)?.qualifiedName ==
                "kotlinx.serialization.internal.SerializationConstructorMarker"
        }

    val constructor =
        klass.primaryConstructor
            ?: klass.constructors.firstOrNull() // If no primary defined, fallback to the first one
            ?: return null

    return if (constructor.isSyntheticSerializationConstructor()) {
        // Find the real constructor without the SerializationConstructorMarker param
        klass.constructors
            .firstOrNull { ctor -> ctor != constructor && !ctor.isSyntheticSerializationConstructor() }
    } else {
        constructor
    }
}

/**
 * Retrieves a list of methods that are implemented by the current function within its class hierarchy.
 */
internal fun KFunction<*>.findImplementedMethods(): List<KFunction<*>> {
    val javaMethod = this.javaMethod ?: return emptyList()
    val methodName = javaMethod.name
    val parameterTypes = javaMethod.parameterTypes

    return javaMethod.declaringClass.kotlin.allSuperclasses
        .toList()
        .mapNotNull { superclass ->
            try {
                @Suppress("SpreadOperator")
                superclass.java
                    .getDeclaredMethod(methodName, *parameterTypes)
                    .kotlinFunction
                    ?.takeIf { it != this }
            } catch (_: NoSuchMethodException) {
                null
            }
        }
}
