//! Setter generation for builder fields
//!
//! Generates typestate-constrained setters for required fields and
//! flexible setters for optional fields.

use crate::codegen::builder_gen::BuilderSchema;
use crate::codegen::builder_gen::state_mod::RequiredField;
use crate::codegen::utils::make_ident;
use heck::{ToPascalCase, ToSnakeCase};
use jacquard_common::deps::smol_str::SmolStr;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};

/// Generate all setters for a builder
pub fn generate_setters(
    codegen: &crate::codegen::CodeGenerator,
    nsid: &str,
    type_name: &str,
    schema: &BuilderSchema,
    required_fields: &[RequiredField],
    has_lifetime: bool,
    has_type_param: bool,
    resolved: &crate::codegen::prettify::ResolvedImports,
) -> TokenStream {
    let builder_name = format_ident!("{}Builder", type_name);
    let state_mod_name = format_ident!("{}_state", type_name.to_snake_case());

    let required_set: std::collections::HashSet<&SmolStr> =
        required_fields.iter().map(|f| &f.name_snake).collect();

    let property_names = schema.property_names();
    let mut setters = Vec::new();

    for (index, field_name) in property_names.iter().enumerate() {
        let is_required = required_set.contains(&SmolStr::new(field_name.to_snake_case()));

        if is_required {
            // Generate required field setter with typestate transition
            let setter = generate_required_setter(
                &builder_name,
                &state_mod_name,
                codegen,
                nsid,
                type_name,
                field_name,
                schema,
                has_lifetime,
                has_type_param,
                index,
                resolved,
            );
            setters.push(setter);
        } else {
            // Generate optional field setters (.field() and .maybe_field())
            let setter = generate_optional_setter(
                &builder_name,
                &state_mod_name,
                codegen,
                nsid,
                type_name,
                field_name,
                schema,
                has_lifetime,
                has_type_param,
                index,
                resolved,
            );
            setters.push(setter);
        }
    }

    quote! {
        #(#setters)*
    }
}

/// Generate a setter for a required field with typestate transition
fn generate_required_setter(
    builder_name: &syn::Ident,
    state_mod_name: &syn::Ident,
    codegen: &crate::codegen::CodeGenerator,
    nsid: &str,
    type_name: &str,
    field_name: &SmolStr,
    schema: &BuilderSchema,
    has_lifetime: bool,
    has_type_param: bool,
    index: usize,
    resolved: &crate::codegen::prettify::ResolvedImports,
) -> TokenStream {
    let phantom = resolved.phantom_data();
    let bosstr_path =
        resolved.external_type_tokens(&crate::codegen::prettify::ExternalImport::BosStr);
    let field_snake = make_ident(&field_name.to_snake_case());
    let field_pascal = format_ident!("{}", field_name.to_pascal_case());
    let transition_type = format_ident!("Set{}", field_name.to_pascal_case());
    let index = syn::Index::from(index);

    // Get the Rust type for this field
    let rust_type = get_field_rust_type(codegen, nsid, type_name, field_name, schema, resolved);
    let option_some = resolved.option_some();

    let lifetime_param = if has_lifetime {
        quote! { 'a, }
    } else {
        quote! {}
    };

    let s_param = if has_type_param {
        quote! {, S: #bosstr_path }
    } else {
        quote! {}
    };

    let s_arg = if has_type_param {
        quote! {, S }
    } else {
        quote! {}
    };

    let phantom_lifetime = if has_lifetime {
        quote! { _lifetime: #phantom, }
    } else {
        quote! {}
    };

    let phantom_s = if has_type_param {
        quote! { _type: #phantom, }
    } else {
        quote! {}
    };

    let doc = format!(" Set the `{}` field (required)", field_name);

    quote! {
        impl<#lifetime_param St #s_param> #builder_name<#lifetime_param St #s_arg>
        where
            St: #state_mod_name::State,
            St::#field_pascal: #state_mod_name::IsUnset,
        {
            #[doc = #doc]
            pub fn #field_snake(
                mut self,
                value: impl Into<#rust_type>,
            ) -> #builder_name<#lifetime_param #state_mod_name::#transition_type<St> #s_arg> {
                self._fields.#index = #option_some(value.into());
                #builder_name {
                    _state: #phantom,
                    _fields: self._fields,
                    #phantom_lifetime
                    #phantom_s
                }
            }
        }
    }
}

/// Generate setters for an optional field
fn generate_optional_setter(
    builder_name: &syn::Ident,
    state_mod_name: &syn::Ident,
    codegen: &crate::codegen::CodeGenerator,
    nsid: &str,
    type_name: &str,
    field_name: &SmolStr,
    schema: &BuilderSchema,
    has_lifetime: bool,
    has_type_param: bool,
    index: usize,
    resolved: &crate::codegen::prettify::ResolvedImports,
) -> TokenStream {
    let bosstr_path =
        resolved.external_type_tokens(&crate::codegen::prettify::ExternalImport::BosStr);
    let field_snake = make_ident(&field_name.to_snake_case());
    let maybe_field_snake = format_ident!("maybe_{}", field_name.to_snake_case());
    let index = syn::Index::from(index);

    // Get the Rust type for this field
    let rust_type = get_field_rust_type(codegen, nsid, type_name, field_name, schema, resolved);

    let lifetime_param = if has_lifetime {
        quote! { 'a, }
    } else {
        quote! {}
    };

    let s_param = if has_type_param {
        quote! {S: #bosstr_path}
    } else {
        quote! {}
    };

    let s_arg = if has_type_param {
        quote! {, S }
    } else {
        quote! {}
    };

    let doc_field = format!(" Set the `{}` field (optional)", field_name);
    let doc_maybe = format!(
        " Set the `{}` field to an Option value (optional)",
        field_name
    );

    quote! {
        impl<#lifetime_param St: #state_mod_name::State, #s_param> #builder_name<#lifetime_param St #s_arg> {
            #[doc = #doc_field]
            pub fn #field_snake(mut self, value: impl Into<Option<#rust_type>>) -> Self {
                self._fields.#index = value.into();
                self
            }

            #[doc = #doc_maybe]
            pub fn #maybe_field_snake(mut self, value: Option<#rust_type>) -> Self {
                self._fields.#index = value;
                self
            }
        }
    }
}

/// Get the Rust type for a field
fn get_field_rust_type(
    codegen: &crate::codegen::CodeGenerator,
    nsid: &str,
    type_name: &str,
    field_name: &SmolStr,
    schema: &BuilderSchema,
    resolved: &crate::codegen::prettify::ResolvedImports,
) -> TokenStream {
    match schema {
        BuilderSchema::Object(obj) => {
            let field_type = &obj.properties[field_name];
            codegen
                .property_to_rust_type(nsid, type_name, field_name, field_type, resolved)
                .unwrap_or_else(|_| quote! { () })
        }
        BuilderSchema::Parameters(params) => {
            let field_type = &params.properties[field_name];
            super::builder_struct::get_params_rust_type(codegen, field_type, resolved)
        }
    }
}
