//! Registry entry generation for struct errors

use proc_macro2::TokenStream;
use quote::quote;

use crate::codegen::helpers::{
    DEFAULT_JSONRPC_CODE, generate_downcast_attempts, generate_field_metadata,
    precompute_code_variants, quote_option_str,
};
use crate::ir::{HttpStatusValue, StructDefinition};

/// Generate the registry entry for a struct error
pub fn generate_registry_entry(ir: &StructDefinition) -> TokenStream {
    // References to external crates
    let private = quote!(::apollo_errors::private);
    let linkme_crate = quote!(#private::linkme);
    let serde_json_crate = quote!(#private::serde_json);

    let name = &ir.name;
    let name_str = name.to_string();
    let message = &ir.error_message;
    let code = &ir.diagnostic_code;
    let http_status_expr = match &ir.http_status {
        Some(HttpStatusValue::Code(code)) => quote! { #code },
        Some(HttpStatusValue::Path(path)) => quote! { #path.as_u16() },
        None => quote! { 500u16 },
    };
    let jsonrpc_code = ir.jsonrpc_code.unwrap_or(DEFAULT_JSONRPC_CODE);

    // Create unique static names
    let static_prefix = format!("_APOLLO_ERROR_{}", name_str.to_uppercase());
    let static_name = syn::Ident::new(&format!("{static_prefix}_REGISTRY_ENTRY"), name.span());
    let fields_static_name = syn::Ident::new(&format!("{static_prefix}_FIELDS"), name.span());
    let variants_static_name = syn::Ident::new(&format!("{static_prefix}_VARIANTS"), name.span());
    let metadata_static_name = syn::Ident::new(&format!("{static_prefix}_METADATA"), name.span());

    // Generate field metadata
    let field_metadata: Vec<_> = ir.fields.iter().map(generate_field_metadata).collect();

    let help = quote_option_str(&ir.help_text);
    let url = quote_option_str(&ir.url);
    let severity = quote_option_str(&ir.severity);

    let (code_screaming_snake, code_camel, code_pascal, code_kebab) =
        precompute_code_variants(code);

    // Generate render function names
    let render_json_fn = syn::Ident::new(
        &format!("{}_render_json", static_prefix.to_lowercase()),
        name.span(),
    );
    let render_html_fn = syn::Ident::new(
        &format!("{}_render_html", static_prefix.to_lowercase()),
        name.span(),
    );
    let render_graphql_fn = syn::Ident::new(
        &format!("{}_render_graphql", static_prefix.to_lowercase()),
        name.span(),
    );
    let render_text_fn = syn::Ident::new(
        &format!("{}_render_text", static_prefix.to_lowercase()),
        name.span(),
    );
    let http_status_fn = syn::Ident::new(
        &format!("{}_http_status", static_prefix.to_lowercase()),
        name.span(),
    );
    let render_jsonrpc_fn = syn::Ident::new(
        &format!("{}_render_jsonrpc", static_prefix.to_lowercase()),
        name.span(),
    );
    let http_headers_fn = syn::Ident::new(
        &format!("{}_http_headers", static_prefix.to_lowercase()),
        name.span(),
    );

    // Generate downcast bodies
    let json_body = generate_downcast_attempts(name, "to_json", true);
    let html_body = generate_downcast_attempts(name, "to_html", true);
    let graphql_body = generate_downcast_attempts(name, "to_graphql", true);
    let text_body = generate_downcast_attempts(name, "to_text", true);
    let http_status_body = generate_downcast_attempts(name, "http_status", false);
    let jsonrpc_body = generate_downcast_attempts(name, "to_jsonrpc", true);
    let http_headers_body = generate_downcast_attempts(name, "http_headers", false);

    quote! {
        // Field metadata static
        static #fields_static_name: &[::apollo_errors::private::FieldMetadata] = &[
            #(#field_metadata),*
        ];

        // Variants
        static #variants_static_name: &[::apollo_errors::private::VariantMetadata] =
            &[::apollo_errors::private::VariantMetadata::Regular(
                ::apollo_errors::private::RegularVariantMetadata {
                    name: #name_str,
                    message: #message,
                    code: #code,
                    code_screaming_snake: #code_screaming_snake,
                    code_camel: #code_camel,
                    code_pascal: #code_pascal,
                    code_kebab: #code_kebab,
                    http_status: #http_status_expr,
                    jsonrpc_code: #jsonrpc_code,
                    help: #help,
                    url: #url,
                    severity: #severity,
                    fields: #fields_static_name,
                },
            )];

        // Error metadata
        static #metadata_static_name: ::apollo_errors::private::ErrorMetadata =
            ::apollo_errors::private::ErrorMetadata {
                type_name: #name_str,
                variants: #variants_static_name,
            };

        // Render functions
        fn #render_json_fn(error: &(dyn std::error::Error + 'static), config: ::apollo_errors::private::FormatConfig) -> Option<::std::result::Result<#serde_json_crate::Value, #serde_json_crate::Error>> {
            #json_body
        }

        fn #render_html_fn(error: &(dyn std::error::Error + 'static), config: ::apollo_errors::private::FormatConfig) -> Option<String> {
            #html_body
        }

        fn #render_graphql_fn(error: &(dyn std::error::Error + 'static), config: ::apollo_errors::private::FormatConfig) -> Option<::std::result::Result<#serde_json_crate::Value, #serde_json_crate::Error>> {
            #graphql_body
        }

        fn #render_text_fn(error: &(dyn std::error::Error + 'static), config: ::apollo_errors::private::FormatConfig) -> Option<String> {
            #text_body
        }

        fn #http_status_fn(error: &(dyn std::error::Error + 'static)) -> Option<::apollo_errors::http::StatusCode> {
            #http_status_body
        }

        fn #render_jsonrpc_fn(error: &(dyn std::error::Error + 'static), config: ::apollo_errors::private::FormatConfig) -> Option<::std::result::Result<#serde_json_crate::Value, #serde_json_crate::Error>> {
            #jsonrpc_body
        }

        fn #http_headers_fn(error: &(dyn std::error::Error + 'static)) -> Option<Vec<(::apollo_errors::http::HeaderName, ::apollo_errors::http::HeaderValue)>> {
            #http_headers_body
        }

        #[#linkme_crate::distributed_slice(::apollo_errors::private::ERROR_REGISTRY)]
        #[linkme(crate = #linkme_crate)]
        #[doc(hidden)]
        static #static_name: ::apollo_errors::private::ErrorRegistryEntry =
            ::apollo_errors::private::ErrorRegistryEntry {
                type_id: std::any::TypeId::of::<#name>(),
                type_name: #name_str,
                metadata: &#metadata_static_name,
                render_json: #render_json_fn,
                render_html: #render_html_fn,
                render_graphql: #render_graphql_fn,
                render_text: #render_text_fn,
                http_status: #http_status_fn,
                render_jsonrpc: #render_jsonrpc_fn,
                http_headers: #http_headers_fn,
            };
    }
}
