#![warn(clippy::all)]
#![warn(clippy::nursery)]
#![warn(clippy::pedantic)]
#![warn(missing_docs)]
#![warn(rust_2018_idioms)]
#![deny(unsafe_code)]
// Allow some overly strict pedantic lints
#![allow(clippy::too_many_lines)]
#![allow(clippy::similar_names)]

//! Procedural macros for the Rovo `OpenAPI` documentation framework.
//!
//! This crate provides the `#[rovo]` attribute macro that processes doc comments
//! with special annotations to generate `OpenAPI` documentation automatically.

use proc_macro::TokenStream;
use quote::{quote, quote_spanned};

mod parser;
mod utils;

use parser::parse_rovo_function;

/// Macro that generates `OpenAPI` documentation from doc comments.
///
/// This macro automatically generates `OpenAPI` documentation for your handlers
/// using doc comments with special annotations.
///
/// # Documentation Format
///
/// Use Rust-style doc comment sections and metadata annotations:
///
/// ## Sections
/// - `# Responses` - Document response status codes
/// - `# Examples` - Provide example responses
/// - `# Metadata` - Add tags, security, and other metadata
///
/// ## Metadata Annotations
/// - `@tag <tag_name>` - Add a tag for grouping operations (can be used multiple times)
/// - `@security <scheme_name>` - Add security requirements (can be used multiple times)
/// - `@id <operation_id>` - Set a custom operation ID (defaults to function name)
/// - `@hidden` - Hide this operation from documentation
/// - `@rovo-ignore` - Stop processing annotations after this point
///
/// Additionally, the Rust `#[deprecated]` attribute is automatically detected
/// and will mark the operation as deprecated in the `OpenAPI` spec.
///
/// # Example
///
/// ```rust,ignore
/// /// Get a single Todo item.
/// ///
/// /// Retrieve a Todo item by its ID from the database.
/// ///
/// /// # Responses
/// ///
/// /// 200: Json<TodoItem> - Successfully retrieved the todo item
/// /// 404: () - Todo item was not found
/// ///
/// /// # Examples
/// ///
/// /// 200: TodoItem::default()
/// ///
/// /// # Metadata
/// ///
/// /// @tag todos
/// #[rovo]
/// async fn get_todo(
///     State(app): State<AppState>,
///     Path(todo): Path<SelectTodo>
/// ) -> impl IntoApiResponse {
///     // ...
/// }
///
/// /// This is a deprecated endpoint.
/// ///
/// /// # Metadata
/// ///
/// /// @tag admin
/// /// @security bearer_auth
/// #[deprecated]
/// #[rovo]
/// async fn old_handler() -> impl IntoApiResponse {
///     // ...
/// }
/// ```
#[proc_macro_attribute]
pub fn rovo(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = item;

    match parse_rovo_function(input.into()) {
        Ok((func_item, doc_info)) => {
            let func_name = &func_item.name;

            let title = doc_info.title.as_deref().unwrap_or("");
            let description = doc_info.description.as_deref().unwrap_or("");

            // Generate response setters if we have doc comments
            let response_code_setters = if doc_info.responses.is_empty() {
                // No responses specified - generate a minimal docs function
                vec![]
            } else {
                doc_info
                    .responses
                    .iter()
                    .map(|resp| {
                        let code = resp.status_code;
                        let response_type = &resp.response_type;
                        let desc = &resp.description;

                        // Check if there's an explicit example for this status code
                        doc_info
                            .examples
                            .iter()
                            .find(|e| e.status_code == code)
                            .map_or_else(
                                || {
                                    // No explicit example, just add the description
                                    quote! {
                                        .response_with::<#code, #response_type, _>(|res| {
                                            res.description(#desc)
                                        })
                                    }
                                },
                                |example| {
                                    let example_code = &example.example_code;
                                    quote! {
                                        .response_with::<#code, #response_type, _>(|res| {
                                            res.description(#desc)
                                                .example(#example_code)
                                        })
                                    }
                                },
                            )
                    })
                    .collect()
            };

            // Generate tag setters
            let tag_setters: Vec<_> = doc_info
                .tags
                .iter()
                .map(|tag| {
                    quote! { .tag(#tag) }
                })
                .collect();

            // Generate security requirement setters
            let security_setters: Vec<_> = doc_info
                .security_requirements
                .iter()
                .map(|scheme| {
                    quote! { .security_requirement(#scheme) }
                })
                .collect();

            // Generate operation ID setter
            let operation_id_setter = doc_info.operation_id.as_ref().map_or_else(
                || {
                    // Default to function name if no custom ID provided
                    let default_id = func_name.to_string();
                    quote! { .id(#default_id) }
                },
                |id| quote! { .id(#id) },
            );

            // Generate deprecated setter
            let deprecated_setter = if doc_info.deprecated {
                quote! { .with(|mut op| { op.inner_mut().deprecated = true; op }) }
            } else {
                quote! {}
            };

            // Generate hidden setter
            let hidden_setter = if doc_info.hidden {
                quote! { .hidden(true) }
            } else {
                quote! {}
            };

            // Generate an internal implementation name
            let impl_name = quote::format_ident!("__{}_impl", func_name);

            // Get the renamed function tokens
            let impl_func = func_item.with_renamed(&impl_name);

            // Create a const with an uppercase version of the handler name
            let const_name = quote::format_ident!("{}", func_name.to_string().to_uppercase());

            // Determine the state type for the trait implementation
            let state_type = func_item
                .state_type
                .as_ref()
                .map_or_else(|| quote! { () }, |st| quote! { #st });

            let output = quote! {
                // Internal implementation with renamed function
                #[allow(non_snake_case, private_interfaces)]
                #impl_func

                // Create a zero-sized type that can be passed to routing functions
                #[allow(non_camel_case_types)]
                #[derive(Clone, Copy)]
                pub struct #func_name;

                impl #func_name {
                    #[doc(hidden)]
                    pub fn __docs(op: aide::transform::TransformOperation) -> aide::transform::TransformOperation {
                        op
                            #operation_id_setter
                            .summary(#title)
                            .description(#description)
                            #(#tag_setters)*
                            #deprecated_setter
                            #hidden_setter
                            #(#security_setters)*
                            #(#response_code_setters)*
                    }
                }

                // Implement the IntoApiMethodRouter trait
                impl ::rovo::IntoApiMethodRouter<#state_type> for #func_name {
                    fn into_get_route(self) -> aide::axum::routing::ApiMethodRouter<#state_type> {
                        aide::axum::routing::get_with(#impl_name, Self::__docs)
                    }

                    fn into_post_route(self) -> aide::axum::routing::ApiMethodRouter<#state_type> {
                        aide::axum::routing::post_with(#impl_name, Self::__docs)
                    }

                    fn into_patch_route(self) -> aide::axum::routing::ApiMethodRouter<#state_type> {
                        aide::axum::routing::patch_with(#impl_name, Self::__docs)
                    }

                    fn into_delete_route(self) -> aide::axum::routing::ApiMethodRouter<#state_type> {
                        aide::axum::routing::delete_with(#impl_name, Self::__docs)
                    }

                    fn into_put_route(self) -> aide::axum::routing::ApiMethodRouter<#state_type> {
                        aide::axum::routing::put_with(#impl_name, Self::__docs)
                    }
                }

                // Also create a CONST for explicit use
                #[allow(non_upper_case_globals)]
                pub const #const_name: #func_name = #func_name;
            };

            output.into()
        }
        Err(err) => {
            let err_msg = err.to_string();
            // Use the span from the error if available, otherwise use call_site
            let error_tokens = err.span().map_or_else(
                || {
                    quote! {
                        compile_error!(#err_msg);
                    }
                },
                |span| {
                    quote_spanned! {span=>
                        compile_error!(#err_msg);
                    }
                },
            );
            error_tokens.into()
        }
    }
}
