use axum::{Json, Router, extract::State, routing::get};
use utoipa::OpenApi;

use crate::{
    AppState, admin, admin_authorities, admin_objects, admin_search, admin_vocab, config, health,
    public,
};

#[derive(OpenApi)]
#[openapi(
    paths(
        config::get_config,
        health::live,
        health::ready,
        public::list_objects,
        public::get_object,
        admin::login,
        admin::logout,
        admin::me,
        admin::list_users,
        admin::set_visibility,
        admin_objects::list_objects,
        admin_objects::get_object,
        admin_objects::create_object,
        admin_objects::update_object,
        admin_objects::delete_object,
        admin_objects::list_field_definitions,
        admin_objects::create_field_definition,
        admin_objects::update_field_definition,
        admin_objects::delete_field_definition,
        admin_objects::set_fields,
        admin_vocab::list_vocabularies,
        admin_vocab::create_vocabulary,
        admin_vocab::list_terms,
        admin_vocab::add_term,
        admin_vocab::update_term,
        admin_vocab::delete_term,
        admin_vocab::rename_vocabulary,
        admin_vocab::delete_vocabulary,
        admin_search::search_objects,
        admin_authorities::list_authorities,
        admin_authorities::create_authority,
        admin_authorities::update_authority,
        admin_authorities::delete_authority
    ),
    components(schemas(
        config::ConfigView,
        health::Live,
        health::Ready,
        public::PublicView,
        public::PublicObjectPage,
        admin::LoginRequest,
        admin::UserView,
        admin::VisibilityRequest,
        admin_objects::AdminObjectView,
        admin_objects::AdminObjectPage,
        admin_objects::LabelView,
        admin_objects::ObjectCreateRequest,
        admin_objects::ObjectUpdateRequest,
        admin_objects::CreatedObject,
        admin_objects::FieldDefinitionView,
        admin_objects::NewFieldDefinitionRequest,
        admin_objects::UpdateFieldDefinitionRequest,
        admin_objects::CreatedField,
        admin_objects::FieldErrorView,
        admin_vocab::VocabularyView,
        admin_vocab::NewVocabularyRequest,
        admin_vocab::NewTermRequest,
        admin_vocab::LabelInput,
        admin_vocab::TermView,
        admin_vocab::CreatedId,
        admin_vocab::UpdateTermRequest,
        admin_vocab::InUseView,
        admin_vocab::RenameVocabularyRequest,
        admin_search::SearchHitView,
        admin_search::SearchResultsView,
        admin_authorities::AuthorityView,
        admin_authorities::NewAuthorityRequest,
        admin_authorities::UpdateAuthorityRequest,
        domain::Visibility,
        domain::AuthorityKind,
        domain::DataType
    )),
    info(title = "Collection Management System", version = "0.0.0")
)]
struct ApiDoc;

/// Serve the OpenAPI document, overriding the title from runtime config so the
/// product name is never hardcoded.
async fn openapi_json(State(state): State<AppState>) -> Json<utoipa::openapi::OpenApi> {
    let mut doc = ApiDoc::openapi();

    doc.info.title = state.app_name.clone();

    Json(doc)
}

/// OpenAPI routes, parameterized over [`AppState`].
pub(crate) fn routes() -> Router<AppState> {
    Router::new().route("/api-docs/openapi.json", get(openapi_json))
}
