/*
 * Copyright 2025 the original author or authors.
 *
 * Licensed 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.gradle.internal.declarativedsl.interpreter.defaults

import org.gradle.api.Plugin
import org.gradle.declarative.dsl.evaluation.EvaluationSchema
import org.gradle.declarative.dsl.schema.CustomAccessorIdentifier.ProjectFeatureIdentifier
import org.gradle.internal.declarativedsl.analysis.AssignmentRecord
import org.gradle.internal.declarativedsl.analysis.DefaultProjectFeatureAccessorIdentifier
import org.gradle.internal.declarativedsl.analysis.ObjectOrigin
import org.gradle.internal.declarativedsl.analysis.ResolutionResult
import org.gradle.internal.declarativedsl.analysis.ResolutionTrace
import org.gradle.internal.declarativedsl.defaults.projectFeatureRegistryBasedModelDefaultsRepository
import org.gradle.internal.declarativedsl.evaluationSchema.InterpretationSchemaBuilder
import org.gradle.internal.declarativedsl.evaluator.conversion.AnalysisAndConversionStepRunner
import org.gradle.internal.declarativedsl.evaluator.conversion.ConversionStepContext
import org.gradle.internal.declarativedsl.evaluator.defaults.ApplyModelDefaultsHandler
import org.gradle.internal.declarativedsl.evaluator.defaults.ModelDefaultsRepository
import org.gradle.internal.declarativedsl.evaluator.defaults.ModelDefaultsResolutionResults
import org.gradle.internal.declarativedsl.evaluator.runner.AbstractAnalysisStepRunner
import org.gradle.internal.declarativedsl.evaluator.runner.AnalysisStepContext
import org.gradle.internal.declarativedsl.evaluator.runner.EvaluationResult.Evaluated
import org.gradle.internal.declarativedsl.evaluator.runner.EvaluationResult.NotEvaluated
import org.gradle.internal.declarativedsl.evaluator.runner.ParseAndResolveResult
import org.gradle.internal.declarativedsl.evaluator.schema.DeclarativeScriptContext
import org.gradle.internal.declarativedsl.evaluator.schema.InterpretationSchemaBuildingResult
import org.gradle.internal.declarativedsl.interpreter.DeclarativeDslNotEvaluatedException
import org.gradle.internal.declarativedsl.language.AssignmentLikeStatement
import org.gradle.internal.declarativedsl.language.Block
import org.gradle.internal.declarativedsl.language.Expr
import org.gradle.internal.declarativedsl.language.LanguageTreeResult
import org.gradle.internal.declarativedsl.language.SyntheticallyProduced
import org.gradle.internal.declarativedsl.project.PROJECT_INTERPRETATION_SEQUENCE_STEP_KEY
import org.gradle.features.internal.binding.ModelDefaultsApplicator.ClassLoaderContext
import org.gradle.features.internal.binding.ModelDefaultsHandler
import org.gradle.features.internal.binding.ProjectFeatureDeclarations
import javax.inject.Inject


/**
 * A {@link ConventionHandler} for applying declarative conventions.
 */
abstract class DeclarativeModelDefaultsHandler @Inject constructor(
    val projectFeatureDeclarations: ProjectFeatureDeclarations,
    interpretationSchemaBuilder: InterpretationSchemaBuilder
) : ModelDefaultsHandler {
    private
    val step by lazy {
        val schema = interpretationSchemaBuilder.getEvaluationSchemaForScript(DeclarativeScriptContext.ProjectScript)
        check(schema is InterpretationSchemaBuildingResult.InterpretationSequenceAvailable) { "expected a project schema to be built" }
        schema.sequence.steps.single { it.stepIdentifier.key == PROJECT_INTERPRETATION_SEQUENCE_STEP_KEY }
    }
    private
    val modelDefaultsRepository = projectFeatureRegistryBasedModelDefaultsRepository(projectFeatureDeclarations)

    override fun apply(target: Any, definition: Any, classLoaderContext: ClassLoaderContext, projectFeatureName: String, plugin: Plugin<*>) {
        // TODO - this works because there should only be one implementation for each project type.  We'll need to revisit this
        // when we support defaults for project features which can have multiple implementations.
        val projectFeature = projectFeatureDeclarations.projectFeatureImplementations.getValue(projectFeatureName).first()

        val analysisStepRunner = ApplyDefaultsOnlyAnalysisStepRunner()
        val analysisStepContext = AnalysisStepContext(
            emptySet(),
            setOf(SingleProjectTypeApplyModelDefaultsHandler(modelDefaultsRepository, DefaultProjectFeatureAccessorIdentifier(projectFeature.featureName, projectFeature.targetDefinitionType.targetClassName)))
        )

        val result = AnalysisAndConversionStepRunner(analysisStepRunner)
            .runInterpretationSequenceStep(
                "<none>",
                "",
                step,
                ConversionStepContext(
                    target,
                    { classLoaderContext.classLoader },
                    { classLoaderContext.parentClassLoader },
                    analysisStepContext,
                    false
                )
            )

        when (result) {
            is Evaluated -> Unit
            is NotEvaluated -> {
                // We shouldn't get any stage failures here, as we're only applying conventions that have already been analyzed as part of the
                // settings script evaluation.  However, if we do for some reason, we'll throw an exception.
                throw DeclarativeDslNotEvaluatedException("", result.stageFailures)
            }
        }
    }
}


private
class ApplyDefaultsOnlyAnalysisStepRunner : AbstractAnalysisStepRunner() {
    override fun parseAndResolve(evaluationSchema: EvaluationSchema, scriptIdentifier: String, scriptSource: String): ParseAndResolveResult {
        // Create a synthetic top level receiver
        val topLevelBlock = Block(emptyList(), SyntheticallyProduced)
        val languageTreeResult = LanguageTreeResult(emptyList(), topLevelBlock, emptyList(), emptyList())
        val topLevelReceiver = ObjectOrigin.TopLevelReceiver(evaluationSchema.analysisSchema.topLevelReceiverType, topLevelBlock)

        return ParseAndResolveResult(languageTreeResult, emptyResolutionResultForReceiver(topLevelReceiver), emptyResolutionTrace(), emptyList())
    }
}


private
fun emptyResolutionTrace() = object : ResolutionTrace {
    override fun assignmentResolution(assignment: AssignmentLikeStatement): ResolutionTrace.ResolutionOrErrors<AssignmentRecord> {
        throw UnsupportedOperationException()
    }

    override fun expressionResolution(expr: Expr): ResolutionTrace.ResolutionOrErrors<ObjectOrigin> {
        throw UnsupportedOperationException()
    }
}


private
fun emptyResolutionResultForReceiver(receiver: ObjectOrigin.TopLevelReceiver) = ResolutionResult(
    receiver,
    emptyList(),
    emptyList(),
    emptyList(),
    emptyList()
)


private
class SingleProjectTypeApplyModelDefaultsHandler(val modelDefaultsRepository: ModelDefaultsRepository, val projectTypeId: ProjectFeatureIdentifier) : ApplyModelDefaultsHandler {
    override fun getDefaultsResolutionResults(resolutionResult: ResolutionResult): List<ModelDefaultsResolutionResults> {
        return listOf(modelDefaultsRepository.findDefaults(projectTypeId)).requireNoNulls()
    }
}
