//! Routes macro for typed handlers with OpenAPI generation.
//!
//! New flat syntax with typed inputs:
//! ```ignore
//! routes! {
//!     GET "/users" => list_users(query: ListQuery) -> Vec<User>,
//!     POST "/users" => create_user(body: CreateUserInput) -> User,
//!     GET "/users/{id}" => get_user(path: Id) -> User,
//!     PUT "/users/{id}" => update_user(path: Id, body: UpdateUser) -> User,
//!     DELETE "/users/{id}" => delete_user(path: Id),
//! }
//! ```

pub mod codegen;
pub mod types;

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::parse_macro_input;

use crate::openapi::generate_openapi_json;
use codegen::generate_route_block;
use types::RoutesDef;

// =============================================================================
// MAIN IMPLEMENTATION
// =============================================================================

#[allow(clippy::too_many_lines)]
pub fn routes_impl(input: TokenStream) -> TokenStream {
    let defs = parse_macro_input!(input as RoutesDef);

    // Validate for duplicate routes (same method + pattern)
    {
        use std::collections::HashSet;
        let mut seen: HashSet<(&str, &str)> = HashSet::new();
        for route in &defs.routes {
            let method_str = route.method.as_str();
            for pattern in &route.patterns {
                if !seen.insert((method_str, pattern.as_str())) {
                    return syn::Error::new_spanned(
                        &route.handler,
                        format!(
                            "Duplicate route: {} \"{}\" is already defined. Each method + pattern combination must be unique.",
                            method_str.to_uppercase(),
                            pattern
                        )
                    )
                    .to_compile_error()
                    .into();
                }
            }
        }
    }

    let route_blocks: Vec<TokenStream2> = defs.routes.iter().map(generate_route_block).collect();

    let openapi_static = generate_openapi_json(&defs.routes);

    let tokens = quote! {
        // Compile-time check: ensure bindings module is properly configured.
        // If you see an error here, make sure you have:
        //   1. `mod bindings;` at the top of your lib.rs
        //   2. Generated bindings via cargo-component build
        //   3. The bindings module exports `mik::core::handler::{Guest, Response, RequestData, Method}`
        const _: () = {
            // This const assertion verifies the Guest trait is accessible
            fn __mik_check_bindings_setup() {
                fn __check<T: handler::Guest>() {}
            }
        };

        /// OpenAPI 3.0 specification as a static string (generated at compile time).
        ///
        /// Note: This constant is excluded from WASM builds to reduce binary size.
        /// It is only available when building for native targets (e.g., running tests).
        ///
        /// Prefer using the `__mik_schema` module for programmatic access.
        #[cfg(not(target_arch = "wasm32"))]
        pub const OPENAPI_JSON: &str = #openapi_static;

        /// Schema utilities for tooling and test discovery.
        ///
        /// This module is auto-generated by the `routes!` macro and provides:
        /// - `HAS_SCHEMA`: Marker constant for discovery
        /// - `write_to(path)`: Write schema to file
        /// - `as_bytes()`: Get schema as bytes
        ///
        /// Only available on native targets (excluded from WASM builds).
        #[cfg(not(target_arch = "wasm32"))]
        pub mod __mik_schema {
            /// Marker constant for tooling discovery.
            /// Presence indicates this crate has an OpenAPI schema.
            pub const HAS_SCHEMA: bool = true;

            /// Write OpenAPI schema to a file.
            ///
            /// # Example
            /// ```ignore
            /// __mik_schema::write_to("openapi.json".as_ref()).unwrap();
            /// ```
            #[inline]
            pub fn write_to(path: &::std::path::Path) -> ::std::io::Result<()> {
                ::std::fs::write(path, super::OPENAPI_JSON)
            }

            /// Get schema as bytes (for programmatic access).
            #[inline]
            pub fn as_bytes() -> &'static [u8] {
                super::OPENAPI_JSON.as_bytes()
            }

            /// Get schema as string.
            #[inline]
            pub fn as_str() -> &'static str {
                super::OPENAPI_JSON
            }
        }

        /// Auto-generated test for schema extraction.
        /// Run with: `cargo test __mik_write_schema`
        #[cfg(all(not(target_arch = "wasm32"), test))]
        #[test]
        fn __mik_write_schema() {
            __mik_schema::write_to(::std::path::Path::new("openapi.json"))
                .expect("Failed to write openapi.json");
            println!("Generated openapi.json ({} bytes)", OPENAPI_JSON.len());
        }

        /// Handler for /__schema endpoint - returns OpenAPI JSON.
        #[cfg(not(target_arch = "wasm32"))]
        #[inline]
        pub fn __schema(_req: &mik_sdk::Request) -> handler::Response {
            handler::Response {
                status: 200,
                headers: vec![
                    (
                        mik_sdk::constants::HEADER_CONTENT_TYPE.to_string(),
                        mik_sdk::constants::MIME_JSON.to_string()
                    ),
                ],
                body: Some(OPENAPI_JSON.as_bytes().to_vec()),
            }
        }

        struct Handler;

        impl Guest for Handler {
            fn handle(__mik_raw: handler::RequestData) -> handler::Response {
                let __mik_method = match __mik_raw.method {
                    handler::Method::Get => mik_sdk::Method::Get,
                    handler::Method::Post => mik_sdk::Method::Post,
                    handler::Method::Put => mik_sdk::Method::Put,
                    handler::Method::Patch => mik_sdk::Method::Patch,
                    handler::Method::Delete => mik_sdk::Method::Delete,
                    handler::Method::Head => mik_sdk::Method::Head,
                    handler::Method::Options => mik_sdk::Method::Options,
                };

                let __mik_path = __mik_raw.path.split('?').next().unwrap_or(&__mik_raw.path);

                // Check for /__schema route first (only on native targets)
                #[cfg(not(target_arch = "wasm32"))]
                if __mik_path == "/__schema" {
                    let __mik_req = mik_sdk::Request::new(
                        __mik_method,
                        __mik_raw.path.clone(),
                        __mik_raw.headers.clone(),
                        __mik_raw.body.clone(),
                        ::std::collections::HashMap::new(),
                    );
                    return __schema(&__mik_req);
                }

                #(#route_blocks)*

                // No route matched - return 404
                handler::Response {
                    status: 404,
                    headers: vec![
                        (
                            mik_sdk::constants::HEADER_CONTENT_TYPE.to_string(),
                            mik_sdk::constants::MIME_PROBLEM_JSON.to_string()
                        )
                    ],
                    body: Some(mik_sdk::json::obj()
                        .set("type", mik_sdk::json::str("about:blank"))
                        .set("title", mik_sdk::json::str(mik_sdk::constants::status_title(404)))
                        .set("status", mik_sdk::json::int(404))
                        .set("detail", mik_sdk::json::str("Route not found"))
                        .to_bytes()),
                }
            }
        }

        // Allow unsafe_code for generated WIT bindings export macro
        #[allow(unsafe_code)]
        const _: () = { bindings::export!(Handler with_types_in bindings); };
    };

    TokenStream::from(tokens)
}
