/*
 * Copyright 2010-2024 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.analysis.api.impl.base.test.cases.components.visibilityChecker

import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.renderer.declarations.impl.KaDeclarationRendererForDebug
import org.jetbrains.kotlin.analysis.api.symbols.KaDeclarationSymbol
import org.jetbrains.kotlin.analysis.test.framework.base.AbstractAnalysisApiBasedTest
import org.jetbrains.kotlin.analysis.test.framework.projectStructure.KtTestModule
import org.jetbrains.kotlin.analysis.test.framework.services.ExpressionMarkerProvider
import org.jetbrains.kotlin.analysis.test.framework.services.expressionMarkerProvider
import org.jetbrains.kotlin.analysis.test.framework.targets.getSingleTestTargetSymbolOfType
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.assertions

/**
 * To find the element for the use-site position, the visibility checker test looks for an element called "useSite" in the main module if
 * the main file doesn't or cannot contain a caret marker, e.g., in files from binary libraries. The target name is case-insensitive, so
 * classes called `UseSite` will be found as well.
 */
private const val USE_SITE_ELEMENT_NAME = "usesite"

/**
 * For local named declaration targets,
 * the callable id cannot be specified in the [TestSymbolTarget][org.jetbrains.kotlin.analysis.test.framework.targets.TestSymbolTarget]
 * directive format.
 * Instead, such declarations can be distinguished by the caret marker `<caret_target>`.
 */
private const val TARGET_QUALIFIER = "target"

/**
 * Checks whether a declaration is visible from a specific use-site file and element.
 *
 * The declaration symbol is found via a symbol name at the bottom of the test file, such as `// class: Declaration` (see
 * [getSingleTestTargetSymbolOfType]) or by the caret marker `<caret_target>`.
 */
abstract class AbstractVisibilityCheckerTest : AbstractAnalysisApiBasedTest() {
    override fun doTestByMainFile(mainFile: KtFile, mainModule: KtTestModule, testServices: TestServices) {
        val actualText = copyAwareAnalyzeForTest(mainFile) { contextFile ->
            val declarationSymbol = findTargetSymbol(contextFile, testServices.expressionMarkerProvider)
            val useSiteElement = findUseSiteElement(contextFile, testServices.expressionMarkerProvider)
            val useSiteFileSymbol = contextFile.symbol
            val visibleByUseSiteVisibilityChecker =
                createUseSiteVisibilityChecker(useSiteFileSymbol, null, useSiteElement).isVisible(declarationSymbol)

            @Suppress("DEPRECATION")
            val isVisibleByDeprecatedVisibilityFunction =
                isVisible(declarationSymbol, useSiteFileSymbol, null, useSiteElement)

            testServices.assertions.assertEquals(isVisibleByDeprecatedVisibilityFunction, visibleByUseSiteVisibilityChecker) {
                "createUseSiteVisibilityChecker(..).isVisible(..) returning $visibleByUseSiteVisibilityChecker is inconsistent with isVisible(...) returning $isVisibleByDeprecatedVisibilityFunction"
            }

            """
                Declaration: ${declarationSymbol.render(KaDeclarationRendererForDebug.WITH_QUALIFIED_NAMES)}
                At usage site: ${useSiteElement.text}
                Is visible: $visibleByUseSiteVisibilityChecker
            """.trimIndent()
        }

        testServices.assertions.assertEqualsToTestOutputFile(actualText)
    }

    private fun KaSession.findTargetSymbol(contextFile: KtFile, provider: ExpressionMarkerProvider): KaDeclarationSymbol {
        val declarationByCaret = provider.getBottommostElementOfTypeAtCaretOrNull<KtNamedDeclaration>(contextFile, TARGET_QUALIFIER)
        return declarationByCaret?.symbol ?: getSingleTestTargetSymbolOfType<KaDeclarationSymbol>(testDataPath, contextFile)
    }

    private fun findUseSiteElement(contextFile: KtFile, provider: ExpressionMarkerProvider): KtExpression {
        return provider.getBottommostElementOfTypeAtCaretOrNull<KtExpression>(contextFile)
            ?: contextFile.findDescendantOfType<KtNamedDeclaration> { it.name?.lowercase() == USE_SITE_ELEMENT_NAME }
            ?: error("Cannot find use-site element to check visibility at.")
    }
}
