DI with colleen

package io.github.cymoo.colleen

// ============================================================================
// Colleen — Dependency Injection API
// ============================================================================
//
// This file shows only the DI-related portion of the Colleen class.
// Paste these members into your existing Colleen class body.
//
// All APIs are fully backward-compatible:
//   - Existing calls without a qualifier continue to work unchanged.
//   - Qualifier support is additive via default parameter `qualifier = null`.
//
// Usage examples at the bottom of this file.
// ============================================================================

class Colleen {

    // ========================================================================
    // Internal container
    // ========================================================================

    @PublishedApi
    internal val serviceContainer = ServiceContainer()

    // ========================================================================
    // provide — register a pre-built instance
    // ========================================================================

    /**
     * Registers a pre-built service instance.
     *
     * ```kotlin
     * // No qualifier (original API — unchanged)
     * app.provide(MyService())
     *
     * // With qualifier
     * app.provide(HikariDataSource(primaryConfig), qualifier = Primary)
     * app.provide(HikariDataSource(replicaConfig), qualifier = Replica)
     * ```
     *
     * @param instance the service instance to register.
     * @param qualifier optional qualifier to distinguish instances of the same type.
     */
    inline fun <reified T : Any> provide(
        instance: T,
        qualifier: Any? = null
    ) {
        serviceContainer.registerInstance(instance, qualifier)
    }

    // ========================================================================
    // provide — register a factory
    // ========================================================================

    /**
     * Registers a service using a factory function.
     *
     * How often the factory is invoked depends on [lifetime]:
     * - [Lifetime.Singleton] *(default)*: factory is invoked lazily and only
     *   once; the same instance is returned for all subsequent resolutions.
     * - [Lifetime.Transient]: factory is invoked on every resolution,
     *   producing a new instance each time.
     *
     * ```kotlin
     * // Original API — all unchanged
     * app.provide { Database.connect(config) }
     * app.provide(Lifetime.Transient) { HttpClient() }
     *
     * // With qualifier (new)
     * app.provide(qualifier = Primary) { HikariDataSource(primaryConfig) }
     * app.provide(qualifier = Replica, lifetime = Lifetime.Singleton) {
     *     HikariDataSource(replicaConfig)
     * }
     * ```
     *
     * @param qualifier optional qualifier to distinguish instances of the same type.
     * @param lifetime defines the lifetime and caching behaviour. Defaults to
     *                 [Lifetime.Singleton].
     * @param factory  a function that creates instances of the service.
     */
    inline fun <reified T : Any> provide(
        qualifier: Any? = null,
        lifetime: Lifetime = Lifetime.Singleton,
        noinline factory: () -> T
    ) {
        when (lifetime) {
            Lifetime.Singleton -> serviceContainer.registerSingleton(qualifier, factory)
            Lifetime.Transient -> serviceContainer.registerTransient(qualifier, factory)
        }
    }
}

// ============================================================================
// Context — service retrieval API
// ============================================================================
//
// Paste these members into your existing Context class body.
// ============================================================================

data class Context(
    val request: Request,
    val response: Response = Response(),
    val app: Colleen,
    val parentContext: Context? = null,
) {

    // ========================================================================
    // getService / getServiceOrNull
    // ========================================================================

    /**
     * Retrieves a required service instance.
     *
     * Resolution order:
     * 1. Current app's service container.
     * 2. Parent contexts (if any).
     *
     * ```kotlin
     * // Original API — unchanged
     * val db = ctx.getService<Database>()
     *
     * // With qualifier (new)
     * val primary = ctx.getService<DataSource>(Primary)
     * val replica  = ctx.getService<DataSource>(Replica)
     * ```
     *
     * @param qualifier optional qualifier identifying the specific registration.
     * @throws IllegalStateException if the service is not registered.
     */
    inline fun <reified T : Any> getService(qualifier: Any? = null): T =
        getServiceOrNull<T>(qualifier)
            ?: error("Service ${T::class.simpleName}(qualifier=$qualifier) not registered")

    /**
     * Retrieves an optional service instance, or `null` if not registered.
     *
     * Resolution order:
     * 1. Current app's service container.
     * 2. Parent contexts (if any).
     *
     * ```kotlin
     * // Original API — unchanged
     * val db = ctx.getServiceOrNull<Database>()
     *
     * // With qualifier (new)
     * val primary = ctx.getServiceOrNull<DataSource>(Primary)
     * ```
     *
     * @param qualifier optional qualifier identifying the specific registration.
     */
    inline fun <reified T : Any> getServiceOrNull(qualifier: Any? = null): T? =
        app.serviceContainer.getOrNull<T>(qualifier)
            ?: parentContext?.getServiceOrNull(qualifier)

    /**
     * Retrieves all registered instances of type [T] from the current app,
     * regardless of qualifier.
     *
     * Useful for plugin-style registrations.
     *
     * ```kotlin
     * val handlers = ctx.getServices<EventHandler>()
     * handlers.forEach { it.handle(event) }
     * ```
     */
    inline fun <reified T : Any> getServices(): List<T> =
        app.serviceContainer.getAll()

    @PublishedApi
    internal inline fun <reified T : Any> getService(kClass: kotlin.reflect.KClass<T>): T? =
        app.serviceContainer.getOrNull(kClass)
            ?: parentContext?.getService(kClass)
}

// ============================================================================
// Usage examples (not compiled — for reference only)
// ============================================================================

/*

// ── Qualifier objects (recommended) ────────────────────────────────────────
object Primary
object Replica

// ── Registration ───────────────────────────────────────────────────────────

// 1. Backward-compatible: no qualifier needed
app.provide { Database.connect(config) }
app.provide(MyService())
app.provide(Lifetime.Transient) { HttpClient() }

// 2. Multiple instances of the same type via qualifier
app.provide<DataSource>(qualifier = Primary) { HikariDataSource(primaryConfig) }
app.provide<DataSource>(qualifier = Replica)  { HikariDataSource(replicaConfig) }

// 3. String qualifier (simple alternative)
app.provide<DataSource>("primary") { HikariDataSource(primaryConfig) }
app.provide<DataSource>("replica") { HikariDataSource(replicaConfig) }

// 4. Plugin-style: multiple handlers registered with distinct qualifiers
app.provide<EventHandler>(qualifier = "audit")   { AuditHandler() }
app.provide<EventHandler>(qualifier = "metrics") { MetricsHandler() }

// ── Retrieval ───────────────────────────────────────────────────────────────

// No qualifier — original API unchanged
val db  = ctx.getService<Database>()
val svc = ctx.getServiceOrNull<MyService>()

// With object qualifier
val primary = ctx.getService<DataSource>(Primary)
val replica  = ctx.getService<DataSource>(Replica)

// With string qualifier
val primary = ctx.getService<DataSource>("primary")

// Get all instances of a type (ignores qualifier)
val handlers = ctx.getServices<EventHandler>()
handlers.forEach { it.handle(event) }

*/