package viaduct.service

import viaduct.api.bootstrap.ViaductTenantAPIBootstrapper
import viaduct.service.api.SchemaId
import viaduct.service.api.Viaduct
import viaduct.service.api.spi.TenantCodeInjector
import viaduct.service.runtime.SchemaConfiguration
import viaduct.service.runtime.StandardViaduct

object BasicViaductFactory {
    /**
     * A simple factory-function for [Viaduct] instances.
     *
     * This function assumes that the class path includes the class
     * and resource files generated by the Viaduct tenant build plugin.
     *
     * @param schemaRegistrationInfo schemas available for serving.  By default, only
     *    the full schema is available under schemaId "" (empty string).
     *
     * @param tenantRegistrationInfo tenant configuration including package prefix and
     *    injector to be used to instantiate resolvers and other tenant code.  The injector
     *    defaults to using reflection and assuming that there are no-arg constructors
     *    for all tenant code.
     */
    fun create(
        schemaRegistrationInfo: SchemaRegistrationInfo = SchemaRegistrationInfo(),
        tenantRegistrationInfo: TenantRegistrationInfo,
    ): Viaduct {
        val builder = builderWithTenantInfo(tenantRegistrationInfo)
        val schemaConfiguration = applySchemaRegistry(schemaRegistrationInfo)
        return builder.withSchemaConfiguration(schemaConfiguration).build()
    }

    // internal for testing
    internal fun builderWithTenantInfo(tenantRegistrationInfo: TenantRegistrationInfo): StandardViaduct.Builder {
        val builder = StandardViaduct.Builder()
            .withTenantAPIBootstrapperBuilder(
                ViaductTenantAPIBootstrapper.Builder()
                    .tenantPackagePrefix(tenantRegistrationInfo.tenantPackagePrefix)
                    .tenantCodeInjector(tenantRegistrationInfo.tenantCodeInjector)
            )
        return builder
    }

    // internal for testing
    internal fun applySchemaRegistry(schemaRegistrationInfo: SchemaRegistrationInfo): SchemaConfiguration {
        val scopeConfigs = schemaRegistrationInfo.scopes.map { scope ->
            if (scope.scopesToApply.isNullOrEmpty()) {
                SchemaConfiguration.ScopeConfig(scope.schemaId, emptySet())
            } else {
                SchemaConfiguration.ScopeConfig(scope.schemaId, scope.scopesToApply)
            }
        }

        return SchemaConfiguration.fromResources(
            packagePrefix = schemaRegistrationInfo.packagePrefix,
            resourcesIncluded = schemaRegistrationInfo.resourcesIncluded?.toRegex(),
            scopes = scopeConfigs.toSet()
        )
    }
}

data class TenantRegistrationInfo(
    val tenantPackagePrefix: String,
    val tenantCodeInjector: TenantCodeInjector = TenantCodeInjector.Naive,
)

/**
 * A descriptor for a (scoped) schema to be registered into a [Viaduct] engine.
 *
 * @param scopes The set of scope defining parameters
 * @param packagePrefix the package prefix to search for the graphqls files, default ""
 * @param resourcesIncluded the regex to match the files to include, default ".*graphqls"
 *  */
data class SchemaRegistrationInfo(
    val scopes: List<SchemaScopeInfo> = listOf(),
    val packagePrefix: String? = null,
    val resourcesIncluded: String? = null,
)

/**
 * A descriptor for a scoped schema configuration.
 *
 * @param schemaId The id for the scoped schema
 * @param scopesToApply The set of scope-ids that define the scoped schema.
 *                      If null or empty, the full schema is used.
 */
data class SchemaScopeInfo(
    val schemaId: String = "",
    val scopesToApply: Set<String>? = null,
)

fun SchemaId.Scoped.toSchemaScopeInfo(): SchemaScopeInfo = SchemaScopeInfo(this.id, this.scopeIds.ifEmpty { null })
