/*
 * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.builtins.functions

import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name

/**
 * [FunctionTypeKind] describes a family of various similar functional types (like kotlin.FunctionN)
 *   All types in the same family have corresponding shape of classId: [packageFqName].[classNamePrefix]N,
 *   where `N` is an arity of function
 *
 * Functional type kind may be either reflect type or non-reflect type
 *   Each non-reflect kind should have corresponding reflect kind and vice-versa (like `kotlin.FunctionN` and `kotlin.reflection.KFunctionN`)
 *
 * Classes for such functional types are synthetic and generated by compiler itself
 * [annotationOnInvokeClassId] is a classId of annotation, which will be added to `invoke` function of generated
 *   functional interface. This argument is mandatory for non-standard functional kinds
 *
 * Assume we have `some.CustomFunctionN` and `some.KCustomFunctionN` families with [annotationOnInvokeClassId] = /some.Anno
 * Classes for those kinds will look like this:
 *
 *   interface CustomFunctionN<P1, P2, ..., PN, R> : kotlin.Function<R> {
 *       @some.Anno
 *       operator fun invoke(p1: P1, p2: P2, ..., pN: PN): R
 *   }
 *
 *   interface KCustomFunctionN<P1, P2, ..., PN, R> : kotlin.reflect.KFunction<R>, some.CustomFunctionN<P1, P2, ..., PN, R> {
 *       @some.Anno
 *       override operator fun invoke(p1: P1, p2: P2, ..., pN: PN): R
 *   }
 *
 * [isInlineable] parameter determines if the specific functional type kind is inlineable or not, which allows the compiler to
 *   properly (not) report diagnostic about inlineability like `DECLARATION_CANT_BE_INLINED`
 *
 * Note that if you provide some new functional type kind it's your responsibility to handle all references to it in backend
 *   with [IrGenerationExtension] implementation
 */
abstract class FunctionTypeKind internal constructor(
    val packageFqName: FqName,
    val classNamePrefix: String,
    val isReflectType: Boolean,
    val annotationOnInvokeClassId: ClassId?,
    val isInlineable: Boolean,
) {
    /*
     * This constructor is needed to enforce not nullable [annotationOnInvokeClassId] for
     *   functional kinds provided by compiler plugins
     */
    constructor(
        packageFqName: FqName,
        classNamePrefix: String,
        annotationOnInvokeClassId: ClassId,
        isReflectType: Boolean,
        isInlineable: Boolean,
    ) : this(packageFqName, classNamePrefix, isReflectType, annotationOnInvokeClassId, isInlineable)

    /*
     * Specifies how corresponding type will be rendered
     * E.g. if `prefixForTypeRender = @Some` and type is `some.CustomFunction2<Int, String, Double>`,
     *   then type will be rendered as `@Some (Int, String) -> Double`
     */
    open val prefixForTypeRender: String?
        get() = null


    /**
     * Specifies the first language version for which to serialize custom function types
     * into kotlin metadata. Until that version, custom function types will be serialized
     * with the legacy scheme: as a FunctionN/KFunctionN with the annotation used for the
     * custom function type.
     *
     * If no version is specified, custom function types are serialized to kotlin metadata.
     *
     * Serialization using the legacy format allows libraries compiled with K2 with a
     * K2 plugin that uses custom function types to be used by clients using a K1 compiler
     * with a K1 compiler plugin that understands the custom function types.
     */
    open val serializeAsFunctionWithAnnotationUntil: String?
        get() = null

    /**
     * Specifies whether the function type kind supports conversion for function references.
     *
     * Type resolution will allow function references with a simple function type (e.g. Function0
     * or KFunction0) to be converted to this function kind if this property is true.
     */
    open val supportsConversionFromSimpleFunctionType: Boolean
        get() = true

    /**
     * @return corresponding non-reflect kind for reflect kind
     * @return [this] if [isReflectType] is false
     *
     * Should be overridden for reflect kinds
     */
    open fun nonReflectKind(): FunctionTypeKind {
        return if (isReflectType) error("Should be overridden explicitly") else this
    }

    /**
     * @return corresponding reflect kind for non-reflect kind
     * @return [this] if [isReflectType] is true
     *
     * Should be overridden for non reflect kinds
     */
    open fun reflectKind(): FunctionTypeKind {
        return if (isReflectType) this else error("Should be overridden explicitly")
    }

    fun numberedClassName(arity: Int): Name = Name.identifier("$classNamePrefix$arity")

    fun numberedClassId(arity: Int): ClassId = ClassId(packageFqName, numberedClassName(arity))

    override fun toString(): String {
        return "$packageFqName.${classNamePrefix}N"
    }

    // ------------------------------------------- Builtin functional kinds -------------------------------------------

    object Function : FunctionTypeKind(
        StandardNames.BUILT_INS_PACKAGE_FQ_NAME,
        "Function",
        isReflectType = false,
        annotationOnInvokeClassId = null,
        isInlineable = true,
    ) {
        override fun reflectKind(): FunctionTypeKind = KFunction
    }

    object SuspendFunction : FunctionTypeKind(
        StandardNames.COROUTINES_PACKAGE_FQ_NAME,
        "SuspendFunction",
        isReflectType = false,
        annotationOnInvokeClassId = null,
        isInlineable = true,
    ) {
        override val prefixForTypeRender: String
            get() = "suspend"

        override fun reflectKind(): FunctionTypeKind = KSuspendFunction
    }

    object KFunction : FunctionTypeKind(
        StandardNames.KOTLIN_REFLECT_FQ_NAME,
        "KFunction",
        isReflectType = true,
        annotationOnInvokeClassId = null,
        isInlineable = false,
    ) {
        override fun nonReflectKind(): FunctionTypeKind = Function
    }

    object KSuspendFunction : FunctionTypeKind(
        StandardNames.KOTLIN_REFLECT_FQ_NAME,
        "KSuspendFunction",
        isReflectType = true,
        annotationOnInvokeClassId = null,
        isInlineable = false,
    ) {
        override fun nonReflectKind(): FunctionTypeKind = SuspendFunction
    }
}

val FunctionTypeKind.isBuiltin: Boolean
    get() = when (this) {
        FunctionTypeKind.Function,
        FunctionTypeKind.SuspendFunction,
        FunctionTypeKind.KFunction,
        FunctionTypeKind.KSuspendFunction -> true
        else -> false
    }

val FunctionTypeKind.isSuspendOrKSuspendFunction: Boolean
    get() = this.nonReflectKind() == FunctionTypeKind.SuspendFunction

val FunctionTypeKind.isBasicFunctionOrKFunction: Boolean
    get() = this.nonReflectKind() == FunctionTypeKind.Function
