/*
 * Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
 * This source file is licensed under the MIT License found in the
 * LICENSE file in the root directory of this source tree.
 */

package simcu::simapi.openapi

import std.reflect.*
import soulsoft_web_http.*
import soulsoft_web_routing.*
import soulsoft_extensions_options.*
import soulsoft_extensions_injection.*
import simcu::simapi.annotations.*

/**
 * @brief 为端点附加 OpenAPI 元数据。
 */
extend EndpointConventionBuilder <: OpenApiEndpointConventionBuilderExtensions {
    /**
     * @brief 为端点附加 API 文档元数据。
     * @param metadata 要附加的 API 文档元数据。
     * @return 当前端点约定构建器实例。
     */
    public func withOpenApi(metadata: SimApiDoc): EndpointConventionBuilder {
        this.add {
            builder => builder.metadata.add(metadata)
        }
        return this
    }
}

/**
 * @brief 映射默认 OpenAPI 文档路由。
 */
extend EndpointRouteBuilder <: OpenApiEndpointRouteBuilderExtension {
    /**
     * @brief 映射指定模式的 OpenAPI 文档路由。
     * @param pattern OpenAPI 文档路由模式。
     * @return 当前端点路由构建器实例。
     */
    public func mapOpenApi(pattern: String): EndpointRouteBuilder {
        verifyOpenApiServicesAreRegistered()
        this.mapGet(pattern) {
            context =>
                let documentName = context.request.routeValues.get("documentName").flatMap {f => f} ?? "v1"
                let documentFactory = context.services.getOrThrow<IDocumentProvider>()
                let document = documentFactory.create(documentName, context.services)
                context.response.contentType = "application/json; charset=utf-8"
                let writer = JsonOpenApiWriter(context.response.body)
                document.serializeAsV3(writer)
                writer.flush()
        }.withOpenApi(SimApiDoc(ignore: true))
        return this
    }

    private func verifyOpenApiServicesAreRegistered() {
        let callSiteFactory = this.services.getOrThrow<IServiceProviderIsService>()
        if (!callSiteFactory.isService<IDocumentProvider>()) {
            throw Exception(
                "Unable to find the required services. Please add all the required services by calling 'ServiceCollection.addOpenApi' inside the call to 'configureServices(...)' in the application startup code.")
        }
    }
}

/**
 * @brief 注册默认名称的 OpenAPI 服务。
 */
extend ServiceCollection <: OpenApiServiceCollectionExtensions {
    /**
     * @brief 注册指定文档名称的 OpenAPI 服务并配置选项。
     * @param documentName OpenAPI 文档名称。
     * @param configureOptions 用于配置 OpenAPI 选项的回调。
     * @return 当前服务集合实例。
     */
    public func addOpenApi(documentName: String, configureOptions: (OpenApiOptions) -> Unit): ServiceCollection {
        this.addRouting()
        this.configure<OpenApiOptions>(documentName, configureOptions)
        this.addSingleton(NamedService(documentName))
        this.addSingleton<IDocumentProvider, OpenApiDocumentProvider>()
        return this
    }
}

/**
 * @brief 启用 OpenAPI UI 中间件。
 */
extend ApplicationBuilder <: OpenApiUIMiddlewareExtensions{
    /**
     * @brief 启用 OpenAPI UI 中间件。
     */
    public func useOpenApiUI(): Unit {
        verifyOpenApiServicesAreRegistered()
        use<OpenApiUIMiddleware>()
    }

    private func verifyOpenApiServicesAreRegistered() {
        let callSiteFactory = this.services.getOrThrow<IServiceProviderIsService>()
        if (!callSiteFactory.isService<IDocumentProvider>()) {
            throw Exception("Unable to find the required services. Please add all the required services by calling 'ServiceCollection.addOpenApi' inside the call to 'configureServices(...)' in the application startup code.")
        }
    }
}

/**
 * @brief 为端点附加认证与响应类型元数据。
 */
extend EndpointConventionBuilder <: SimApiEndpointMetadataExtensions {
    /**
     * @brief 为端点附加 SimApiAuth 认证元数据（使 OpenAPI 文档显示锁图标）。
     */
    public func withSimApiAuth(auth: SimApiAuth): EndpointConventionBuilder {
        this.add {
            builder => builder.metadata.add(auth)
        }
        return this
    }

    /**
     * @brief 为端点附加响应类型元数据（使动态路由也能生成 response schema）。
     */
    public func withResponseType(typeInfo: TypeInfo): EndpointConventionBuilder {
        this.add {
            builder => builder.metadata.add(ApiResponseTypeMetadata(Some(typeInfo)))
        }
        return this
    }
}

/**
 * @brief 提供元数据集合扩展。
 */
extend EndpointMetadataCollection {
    /**
     * @brief 返回最后一个满足条件的元数据。
     * @param filter 用于筛选元数据的条件。
     * @return 最后一个满足条件的元数据；如果不存在则返回 `None`。
     */
    public func getLastMetadata<T>(filter: (T) -> Bool): ?T {
        let metadatas = this.getOrderedMetadata<T>()
        var index = metadatas.size - 1
        while (index >= 0) {
            let metadata = metadatas[index]
            if (filter(metadata)) {
                return metadata
            }
            index--
        }

        return None
    }
}
