use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use tku_core::schema::{AppSchema, ArgType, FlagSchema, OperationSchema, ResourceSchema};

pub struct CodeGenerator<'a> {
    schema: &'a AppSchema,
    selected_tui_profile: Option<String>,
}

#[derive(Clone)]
struct ResourceNode<'a> {
    path: Vec<String>,
    resource: &'a ResourceSchema,
}

impl<'a> CodeGenerator<'a> {
    pub fn new(schema: &'a AppSchema, selected_tui_profile: Option<String>) -> Self {
        Self {
            schema,
            selected_tui_profile,
        }
    }

    pub fn generate_all(&self) -> Vec<(String, String)> {
        vec![
            ("commands.rs".into(), self.fmt(self.gen_commands())),
            ("args.rs".into(), self.fmt(self.gen_args())),
            (
                "handler_traits.rs".into(),
                self.fmt(self.gen_handler_traits()),
            ),
            ("router.rs".into(), self.fmt(self.gen_router())),
            ("tui.rs".into(), self.fmt(self.gen_tui())),
        ]
    }

    fn resource_nodes(&self) -> Vec<ResourceNode<'a>> {
        let mut out = Vec::new();
        for resource in &self.schema.resources {
            Self::collect_resource_nodes(resource, &mut Vec::new(), &mut out);
        }
        out
    }

    fn collect_resource_nodes(
        resource: &'a ResourceSchema,
        parent_path: &mut Vec<String>,
        out: &mut Vec<ResourceNode<'a>>,
    ) {
        parent_path.push(resource.name.clone());
        out.push(ResourceNode {
            path: parent_path.clone(),
            resource,
        });
        for child in &resource.subresources {
            Self::collect_resource_nodes(child, parent_path, out);
        }
        parent_path.pop();
    }

    fn gen_commands(&self) -> TokenStream {
        // Root operation variants go directly into Commands (no wrapping sub-enum).
        let root_variants: Vec<_> = self
            .schema
            .root
            .operations
            .iter()
            .map(|op| {
                let op_ident = pascal_ident(&op.verb);
                let args_ident = args_struct_ident(&["$root".to_string()], op);
                let desc = &op.description;
                quote! { #[doc = #desc] #op_ident(#args_ident) }
            })
            .collect();

        let top_level_variants: Vec<_> = self
            .schema
            .resources
            .iter()
            .map(|resource| self.resource_variant(&[resource.name.clone()], resource))
            .collect();
        let resource_enums: Vec<_> = self
            .schema
            .resources
            .iter()
            .flat_map(|resource| self.resource_enums(resource, Vec::new()))
            .collect();
        let dispatch_arms: Vec<_> = self
            .schema
            .resources
            .iter()
            .map(|resource| self.resource_dispatch_arm(&[resource.name.clone()], resource))
            .collect();

        // Root dispatch arms — match directly on Commands::<Verb>(args).
        let root_dispatch_arms: Vec<_> = self
            .schema
            .root
            .operations
            .iter()
            .map(|op| self.root_operation_dispatch_arm(op))
            .collect();

        let app_name = &self.schema.app.name;
        let app_desc = &self.schema.app.description;
        let app_ver = &self.schema.app.version;

        quote! {
            // Generated by tku-codegen. Do not edit by hand.
            use clap::{Parser, Subcommand};
            use tku_core::prelude::{ArgValue, ParsedArgs};
            use crate::generated::args::*;

            #[derive(Parser)]
            #[command(name = #app_name, about = #app_desc, version = #app_ver)]
            pub struct Cli {
                #[command(subcommand)]
                pub command: Option<Commands>,
                #[arg(long, global = true, default_value = "table")]
                pub format: String,
                /// Execute a command without launching the TUI
                #[arg(short = 'e', long, global = true)]
                pub exec: bool,
                #[arg(short, long, global = true)]
                pub verbose: bool,
            }

            #[derive(Subcommand)]
            pub enum Commands {
                #(#root_variants,)*
                #(#top_level_variants,)*
            }

            #(#resource_enums)*

            pub fn extract_dispatch(command: Commands) -> (String, String, ParsedArgs) {
                match command {
                    #(#root_dispatch_arms)*
                    #(#dispatch_arms)*
                }
            }
        }
    }

    fn resource_enums(
        &self,
        resource: &ResourceSchema,
        mut parent_path: Vec<String>,
    ) -> Vec<TokenStream> {
        parent_path.push(resource.name.clone());
        let mut enums = vec![self.resource_enum(&parent_path, resource)];
        for child in &resource.subresources {
            enums.extend(self.resource_enums(child, parent_path.clone()));
        }
        enums
    }

    fn resource_variant(&self, path: &[String], resource: &ResourceSchema) -> TokenStream {
        let name_ident = pascal_ident(&resource.name);
        let enum_ident = resource_commands_ident(path);
        let desc = &resource.description;
        quote! {
            #[doc = #desc]
            #name_ident {
                #[command(subcommand)]
                command: #enum_ident,
            }
        }
    }

    fn resource_enum(&self, path: &[String], resource: &ResourceSchema) -> TokenStream {
        let enum_ident = resource_commands_ident(path);
        let operation_variants: Vec<_> = resource
            .operations
            .iter()
            .map(|op| {
                let op_ident = pascal_ident(&op.verb);
                let args_ident = args_struct_ident(path, op);
                let desc = &op.description;
                quote! { #[doc = #desc] #op_ident(#args_ident) }
            })
            .collect();
        let subresource_variants: Vec<_> = resource
            .subresources
            .iter()
            .map(|child| {
                let mut child_path = path.to_vec();
                child_path.push(child.name.clone());
                self.resource_variant(&child_path, child)
            })
            .collect();

        quote! {
            #[derive(Subcommand)]
            pub enum #enum_ident {
                #(#operation_variants,)*
                #(#subresource_variants,)*
            }
        }
    }

    fn resource_dispatch_arm(&self, path: &[String], resource: &ResourceSchema) -> TokenStream {
        let resource_ident = pascal_ident(&resource.name);
        let op_arms: Vec<_> = resource
            .operations
            .iter()
            .map(|op| self.operation_dispatch_arm(&path, op))
            .collect();
        let subresource_arms: Vec<_> = resource
            .subresources
            .iter()
            .map(|child| self.subresource_dispatch_arm(path, child))
            .collect();

        quote! {
            Commands::#resource_ident { command } => match command {
                #(#op_arms)*
                #(#subresource_arms)*
            },
        }
    }

    fn subresource_dispatch_arm(
        &self,
        parent_path: &[String],
        resource: &ResourceSchema,
    ) -> TokenStream {
        let variant_ident = pascal_ident(&resource.name);
        let parent_enum_ident = resource_commands_ident(parent_path);
        let mut path = parent_path.to_vec();
        path.push(resource.name.clone());
        let op_arms: Vec<_> = resource
            .operations
            .iter()
            .map(|op| self.operation_dispatch_arm(&path, op))
            .collect();
        let child_arms: Vec<_> = resource
            .subresources
            .iter()
            .map(|child| self.subresource_dispatch_arm(&path, child))
            .collect();

        quote! {
            #parent_enum_ident::#variant_ident { command } => match command {
                #(#op_arms)*
                #(#child_arms)*
            },
        }
    }

    fn operation_dispatch_arm(&self, path: &[String], op: &OperationSchema) -> TokenStream {
        let commands_ident = resource_commands_ident(path);
        let variant_ident = pascal_ident(&op.verb);
        let resource = path.join(".");
        let verb = &op.verb;

        let positional_pushes: Vec<_> = op
            .args
            .iter()
            .map(|a| {
                let field = snake_ident(&a.name);
                if a.required {
                    quote! {
                        parsed.push(args.#field.to_string());
                    }
                } else {
                    quote! {
                        if let Some(value) = args.#field {
                            parsed.push(value.to_string());
                        }
                    }
                }
            })
            .collect();

        let flag_inserts: Vec<_> = op
            .flags
            .iter()
            .map(|f| {
                let field = snake_ident(&f.name);
                let name = &f.name;
                let value = arg_value_expr(&quote!(value), &f.arg_type);
                if f.required || f.default.is_some() {
                    quote! {
                        {
                            let value = args.#field;
                            parsed.insert(#name, #value);
                        }
                    }
                } else {
                    quote! {
                        if let Some(value) = args.#field {
                            parsed.insert(#name, #value);
                        }
                    }
                }
            })
            .collect();

        quote! {
            #commands_ident::#variant_ident(args) => {
                let mut parsed = ParsedArgs::new();
                #(#positional_pushes)*
                #(#flag_inserts)*
                (#resource.to_string(), #verb.to_string(), parsed)
            }
        }
    }

    /// Dispatch arm for a root operation — variant lives directly on `Commands`.
    fn root_operation_dispatch_arm(&self, op: &OperationSchema) -> TokenStream {
        let variant_ident = pascal_ident(&op.verb);
        let verb = &op.verb;

        let positional_pushes: Vec<_> = op
            .args
            .iter()
            .map(|a| {
                let field = snake_ident(&a.name);
                if a.required {
                    quote! { parsed.push(args.#field.to_string()); }
                } else {
                    quote! {
                        if let Some(value) = args.#field {
                            parsed.push(value.to_string());
                        }
                    }
                }
            })
            .collect();

        let flag_inserts: Vec<_> = op
            .flags
            .iter()
            .map(|f| {
                let field = snake_ident(&f.name);
                let name = &f.name;
                let value = arg_value_expr(&quote!(value), &f.arg_type);
                if f.required || f.default.is_some() {
                    quote! {
                        {
                            let value = args.#field;
                            parsed.insert(#name, #value);
                        }
                    }
                } else {
                    quote! {
                        if let Some(value) = args.#field {
                            parsed.insert(#name, #value);
                        }
                    }
                }
            })
            .collect();

        quote! {
            Commands::#variant_ident(args) => {
                let mut parsed = ParsedArgs::new();
                #(#positional_pushes)*
                #(#flag_inserts)*
                ("$root".to_string(), #verb.to_string(), parsed)
            }
        }
    }

    fn gen_args(&self) -> TokenStream {
        let root_args: Vec<_> = self
            .schema
            .root
            .operations
            .iter()
            .map(|op| self.args_struct_and_impl(&["$root".to_string()], op))
            .collect();

        let all: Vec<_> = self
            .resource_nodes()
            .into_iter()
            .flat_map(|node| {
                node.resource
                    .operations
                    .iter()
                    .map(move |op| self.args_struct_and_impl(&node.path, op))
            })
            .collect();

        quote! {
            // Generated args structs + FromArgs impls. Do not edit by hand.
            use clap::Args;
            use tku_core::prelude::{TkucliResult, TkucliError, FromArgs, ParsedArgs};

            #(#root_args)*
            #(#all)*
        }
    }

    fn gen_tui(&self) -> TokenStream {
        let resolved = self
            .schema
            .resolve_tui_profile(self.selected_tui_profile.as_deref())
            .expect("TUI profile should be validated before code generation");

        let profile_name = self
            .selected_tui_profile
            .as_deref()
            .or(self.schema.tui.default_profile.as_deref());
        let selected_profile = match profile_name {
            Some(name) => quote! { Some(#name) },
            None => quote! { None },
        };
        let theme = resolved.theme;
        let default_screen = match resolved.default_screen {
            Some(screen) => quote! { Some(#screen) },
            None => quote! { None },
        };
        let running = resolved.labels.running;
        let latest = resolved.labels.latest;
        let welcome_title = resolved.labels.welcome_title;
        let welcome_body = resolved.labels.welcome_body;
        let tui_enabled = self.schema.tui.enabled;

        quote! {
            // Generated resolved TUI settings. Do not edit by hand.
            use tku_core::schema::AppSchema;
            use tku_tui::screen::ScreenLabels;

            pub const TUI_ENABLED: bool = #tui_enabled;
            pub const SELECTED_PROFILE: Option<&str> = #selected_profile;
            pub const THEME_NAME: &str = #theme;
            pub const DEFAULT_SCREEN: Option<&str> = #default_screen;

            pub fn labels() -> ScreenLabels {
                ScreenLabels {
                    running: Some(#running.to_string()),
                    latest: Some(#latest.to_string()),
                    welcome_title: Some(#welcome_title.to_string()),
                    welcome_body: Some(#welcome_body.to_string()),
                }
            }

            pub fn resolve_schema(mut schema: AppSchema) -> AppSchema {
                schema.tui.theme = THEME_NAME.to_string();
                schema.tui.default_screen = DEFAULT_SCREEN.map(str::to_string);
                schema.tui.default_profile = SELECTED_PROFILE.map(str::to_string);
                schema.tui.labels.running = Some(#running.to_string());
                schema.tui.labels.latest = Some(#latest.to_string());
                schema.tui.labels.welcome_title = Some(#welcome_title.to_string());
                schema.tui.labels.welcome_body = Some(#welcome_body.to_string());
                schema.tui.profiles.clear();
                schema
            }
        }
    }

    fn args_struct_and_impl(&self, path: &[String], op: &OperationSchema) -> TokenStream {
        let struct_ident = args_struct_ident(path, op);

        let positional_fields: Vec<_> = op
            .args
            .iter()
            .map(|a| {
                let field = snake_ident(&a.name);
                let ty = arg_rust_type(&a.arg_type, false);
                let help = a.help.as_deref().unwrap_or("");
                if a.required {
                    quote! { #[arg(help = #help)] pub #field: #ty }
                } else {
                    quote! { #[arg(help = #help)] pub #field: Option<#ty> }
                }
            })
            .collect();

        let flag_fields: Vec<_> = op
            .flags
            .iter()
            .map(|f| {
                let field = snake_ident(&f.name);
                let long_name = &f.name;
                let help = f.help.as_deref().unwrap_or("");
                let default = f.default.as_deref().unwrap_or("");

                if f.required {
                    let ty = flag_rust_type(f, false);
                    quote! {
                        #[arg(long = #long_name, help = #help)]
                        pub #field: #ty
                    }
                } else if !default.is_empty() {
                    let ty = flag_rust_type(f, false);
                    quote! {
                        #[arg(long = #long_name, help = #help, default_value = #default)]
                        pub #field: #ty
                    }
                } else {
                    let ty = flag_rust_type(f, true);
                    quote! {
                        #[arg(long = #long_name, help = #help)]
                        pub #field: #ty
                    }
                }
            })
            .collect();

        let positional_extractions: Vec<_> = op
            .args
            .iter()
            .enumerate()
            .map(|(i, a)| {
                let field = snake_ident(&a.name);
                let name = &a.name;
                let idx = i as u32;
                if a.required {
                    quote! {
                        #field: {
                            let raw = args.positional(#idx as usize)?;
                            raw.parse().map_err(|_| TkucliError::InvalidArgument {
                                name: #name.into(),
                                reason: "failed to parse positional argument".into(),
                            })?
                        }
                    }
                } else {
                    quote! {
                        #field: args.positional(#idx as usize).ok()
                                  .and_then(|s| s.parse().ok())
                    }
                }
            })
            .collect();

        let flag_extractions: Vec<_> = op
            .flags
            .iter()
            .map(|f| {
                let field = snake_ident(&f.name);
                let name = &f.name;
                let default = f.default.as_deref();
                let parse_value = arg_value_parse_expr(&quote!(value), &f.arg_type);
                let parse_required =
                    arg_value_parse_expr(&quote!(args.require(#name)?), &f.arg_type);
                let parse_optional = arg_value_parse_expr(&quote!(value), &f.arg_type);

                if f.required {
                    quote! {
                        #field: {
                            #parse_required?
                        }
                    }
                } else if let Some(d) = default {
                    quote! {
                        #field: args.get(#name)
                            .and_then(|value| { #parse_value }.ok())
                            .unwrap_or_else(|| #d.parse().unwrap())
                    }
                } else {
                    quote! {
                        #field: args.get(#name)
                            .and_then(|value| { #parse_optional }.ok())
                    }
                }
            })
            .collect();

        quote! {
            #[derive(Args, Debug, Clone)]
            pub struct #struct_ident {
                #(#positional_fields,)*
                #(#flag_fields,)*
            }

            impl FromArgs for #struct_ident {
                fn from_args(args: &ParsedArgs) -> TkucliResult<Self> {
                    Ok(Self {
                        #(#positional_extractions,)*
                        #(#flag_extractions,)*
                    })
                }
            }
        }
    }

    fn gen_handler_traits(&self) -> TokenStream {
        let root_trait = if self.schema.root.operations.is_empty() {
            quote! {}
        } else {
            let root_resource = ResourceSchema {
                name: "$root".to_string(),
                description: String::new(),
                operations: self.schema.root.operations.clone(),
                subresources: vec![],
            };
            self.resource_trait(&["$root".to_string()], &root_resource)
        };

        let traits: Vec<_> = self
            .resource_nodes()
            .into_iter()
            .map(|node| self.resource_trait(&node.path, node.resource))
            .collect();

        quote! {
            // Generated handler traits. Implement one per resource.
            use tku_core::prelude::*;
            use crate::generated::args::*;

            #root_trait
            #(#traits)*
        }
    }

    fn resource_trait(&self, path: &[String], resource: &ResourceSchema) -> TokenStream {
        let trait_ident = handler_trait_ident(path);
        let methods: Vec<_> = resource
            .operations
            .iter()
            .map(|op| {
                let method = snake_ident(&op.verb);
                let args_ident = args_struct_ident(path, op);
                quote! {
                    async fn #method(
                        &self,
                        ctx: Ctx,
                        args: #args_ident,
                    ) -> TkucliResult<impl IntoOutput>;
                }
            })
            .collect();

        quote! {
            #[async_trait]
            pub trait #trait_ident: Send + Sync {
                #(#methods)*
            }
        }
    }

    fn gen_router(&self) -> TokenStream {
        let nodes = self.resource_nodes();

        // ── Root handler wiring ────────────────────────────────────────────
        let root_path = ["$root".to_string()];
        let has_root = !self.schema.root.operations.is_empty();

        let root_handler_type = if has_root {
            let accessor = handler_accessor_ident(&root_path);
            let trait_ident = handler_trait_ident(&root_path);
            let assoc_ident = handler_assoc_ident(&root_path);
            quote! {
                type #assoc_ident: #trait_ident + Clone + Send + Sync + 'static;
                fn #accessor(&self) -> Self::#assoc_ident;
            }
        } else {
            quote! {}
        };

        let root_binding = if has_root {
            let binding = handler_binding_ident(&root_path);
            let accessor = handler_accessor_ident(&root_path);
            quote! { let #binding = handlers.#accessor(); }
        } else {
            quote! {}
        };

        let root_confirm_keys: Vec<String> = if has_root {
            self.schema
                .root
                .operations
                .iter()
                .filter(|op| op.confirm)
                .map(|op| format!("$root.{}", op.verb))
                .collect()
        } else {
            vec![]
        };

        let root_registrations: Vec<_> = if has_root {
            let handler_path = handler_binding_ident(&root_path);
            self.schema
                .root
                .operations
                .iter()
                .map(|op| {
                    let verb = &op.verb;
                    let method = snake_ident(&op.verb);
                    let hp = handler_path.clone();
                    quote! {
                        registry.register(tku_core::handler::handler_fn(
                            tku_core::handler::HandlerMeta::new("$root", #verb),
                            {
                                let h = #hp.clone();
                                move |ctx, args| {
                                    let h = h.clone();
                                    async move { h.#method(ctx, args).await.map(|o| o.into_output()) }
                                }
                            },
                        ));
                    }
                })
                .collect()
        } else {
            vec![]
        };

        // ── Regular resource wiring ────────────────────────────────────────
        let handler_types: Vec<_> = nodes
            .iter()
            .map(|node| {
                let accessor = handler_accessor_ident(&node.path);
                let trait_ident = handler_trait_ident(&node.path);
                let assoc_ident = handler_assoc_ident(&node.path);
                quote! {
                    type #assoc_ident: #trait_ident + Clone + Send + Sync + 'static;
                    fn #accessor(&self) -> Self::#assoc_ident;
                }
            })
            .collect();

        let confirm_keys: Vec<String> = root_confirm_keys
            .into_iter()
            .chain(nodes.iter().flat_map(|node| {
                let resource_path = node.path.join(".");
                node.resource
                    .operations
                    .iter()
                    .filter(|op| op.confirm)
                    .map(move |op| format!("{resource_path}.{}", op.verb))
                    .collect::<Vec<_>>()
            }))
            .collect();

        let registrations: Vec<_> = nodes
            .iter()
            .flat_map(|node| {
                let resource_path = node.path.join(".");
                let handler_path = handler_binding_ident(&node.path);
                node.resource.operations.iter().map(move |op| {
                    let verb = &op.verb;
                    let method = snake_ident(&op.verb);
                    quote! {
                        registry.register(tku_core::handler::handler_fn(
                            tku_core::handler::HandlerMeta::new(#resource_path, #verb),
                            {
                                let h = #handler_path.clone();
                                move |ctx, args| {
                                    let h = h.clone();
                                    async move { h.#method(ctx, args).await.map(|o| o.into_output()) }
                                }
                            },
                        ));
                    }
                })
            })
            .collect();

        let handler_bindings: Vec<_> = nodes
            .iter()
            .map(|node| {
                let binding = handler_binding_ident(&node.path);
                let accessor = handler_accessor_ident(&node.path);
                quote! {
                    let #binding = handlers.#accessor();
                }
            })
            .collect();

        let auth_layer = match &self.schema.middleware.auth {
            Some(auth) => {
                let env_var = &auth.env;
                quote! { .layer(tku_core::middleware::AuthLayer::from_env(#env_var)) }
            }
            None => quote! {},
        };
        let confirm_layer = if confirm_keys.is_empty() {
            quote! { .layer(ConfirmLayer::new(Vec::<String>::new())) }
        } else {
            quote! {
                .layer(ConfirmLayer::new([
                    #(#confirm_keys,)*
                ]))
            }
        };

        quote! {
            // Generated router wiring. Do not edit by hand.
            use tku_core::prelude::*;
            use crate::generated::handler_traits::*;
            use std::sync::Arc;

            pub trait Handlers {
                #root_handler_type
                #(#handler_types)*
            }

            pub fn build_router<H>(handlers: H) -> Arc<dyn CliService>
            where
                H: Handlers + Clone + Send + Sync + 'static,
            {
                let mut registry = HandlerRegistry::new();
                #root_binding
                #(#handler_bindings)*

                #(#root_registrations)*
                #(#registrations)*

                Router::new(registry)
                    #auth_layer
                    .layer(LoggingLayer)
                    #confirm_layer
                    .build()
            }
        }
    }

    fn fmt(&self, ts: TokenStream) -> String {
        match syn::parse2::<syn::File>(ts) {
            Ok(file) => prettyplease::unparse(&file),
            Err(e) => panic!("codegen produced invalid Rust: {e}"),
        }
    }
}

fn args_struct_ident(path: &[String], op: &OperationSchema) -> Ident {
    ident(&format!("{}{}Args", path_pascal(path), pascal(&op.verb)))
}

fn handler_trait_ident(path: &[String]) -> Ident {
    ident(&format!("{}Handler", path_pascal(path)))
}

fn handler_assoc_ident(path: &[String]) -> Ident {
    ident(&format!("{}HandlerType", path_pascal(path)))
}

fn handler_accessor_ident(path: &[String]) -> Ident {
    ident(&format!("{}_handler", path_snake(path)))
}

fn handler_binding_ident(path: &[String]) -> Ident {
    ident(&format!("{}_handler", path_snake(path)))
}

fn resource_commands_ident(path: &[String]) -> Ident {
    ident(&format!("{}Commands", path_pascal(path)))
}

fn path_pascal(path: &[String]) -> String {
    path.iter()
        .map(|segment| pascal(segment))
        .collect::<String>()
}

fn path_snake(path: &[String]) -> String {
    path.iter()
        .map(|segment| snake(segment))
        .collect::<Vec<_>>()
        .join("_")
}

fn arg_rust_type(t: &ArgType, optional: bool) -> TokenStream {
    let inner = match t {
        ArgType::String => quote! { String },
        ArgType::U32 => quote! { u32 },
        ArgType::U64 => quote! { u64 },
        ArgType::I64 => quote! { i64 },
        ArgType::F64 => quote! { f64 },
        ArgType::Bool => quote! { bool },
        ArgType::Enum => quote! { String },
    };
    if optional {
        quote! { Option<#inner> }
    } else {
        inner
    }
}

fn arg_value_expr(value: &TokenStream, t: &ArgType) -> TokenStream {
    match t {
        ArgType::String => quote! { ArgValue::String(#value) },
        ArgType::U32 => quote! { ArgValue::U32(#value) },
        ArgType::U64 => quote! { ArgValue::U64(#value) },
        ArgType::I64 => quote! { ArgValue::I64(#value) },
        ArgType::F64 => quote! { ArgValue::F64(#value) },
        ArgType::Bool => quote! { ArgValue::Bool(#value) },
        ArgType::Enum => quote! { ArgValue::Enum(#value) },
    }
}

fn arg_value_parse_expr(value: &TokenStream, t: &ArgType) -> TokenStream {
    match t {
        ArgType::String | ArgType::Enum => quote! { #value.as_str().map(|s| s.to_owned()) },
        ArgType::U32 => quote! { #value.as_u32() },
        ArgType::U64 => quote! { #value.as_u64() },
        ArgType::I64 => quote! { #value.as_i64() },
        ArgType::F64 => quote! { #value.as_f64() },
        ArgType::Bool => quote! { #value.as_bool() },
    }
}

fn flag_rust_type(f: &FlagSchema, optional: bool) -> TokenStream {
    let inner = match f.arg_type {
        ArgType::String => quote! { String },
        ArgType::U32 => quote! { u32 },
        ArgType::U64 => quote! { u64 },
        ArgType::I64 => quote! { i64 },
        ArgType::F64 => quote! { f64 },
        ArgType::Bool => quote! { bool },
        ArgType::Enum => quote! { String },
    };
    if optional {
        quote! { Option<#inner> }
    } else {
        inner
    }
}

fn pascal(s: &str) -> String {
    let words = ident_words(s);
    let mut out = String::new();
    for word in words {
        let mut chars = word.chars();
        match chars.next() {
            None => {}
            Some(first) => {
                out.push(first.to_ascii_uppercase());
                out.extend(chars.map(|c| c.to_ascii_lowercase()));
            }
        }
    }
    if out.is_empty() {
        "Generated".to_string()
    } else if out.chars().next().is_some_and(|c| c.is_ascii_digit()) {
        format!("Generated{out}")
    } else {
        out
    }
}

fn snake(s: &str) -> String {
    let out = ident_words(s).join("_");
    if out.is_empty() {
        "generated".to_string()
    } else if out.chars().next().is_some_and(|c| c.is_ascii_digit()) {
        format!("_{out}")
    } else {
        out
    }
}

fn ident_words(s: &str) -> Vec<String> {
    let mut words = Vec::new();
    let mut current = String::new();

    for ch in s.chars() {
        if ch.is_ascii_alphanumeric() {
            current.push(ch.to_ascii_lowercase());
        } else if !current.is_empty() {
            words.push(std::mem::take(&mut current));
        }
    }

    if !current.is_empty() {
        words.push(current);
    }

    words
}

fn pascal_ident(s: &str) -> Ident {
    Ident::new(&pascal(s), Span::call_site())
}

fn snake_ident(s: &str) -> Ident {
    Ident::new(&snake(s), Span::call_site())
}

fn ident(s: &str) -> Ident {
    Ident::new(s, Span::call_site())
}
