use proc_macro2::TokenStream;
use quote::{quote, format_ident};
use syn::{
    parse::{Parse, ParseStream},
    Expr, Ident, Token, Type, Result as SynResult,
};

/// A single field in the json_store! declaration.
struct JsonStoreField {
    name: Ident,
    ty: Type,
    default: Option<Expr>,
}

/// Parsed input of json_store! macro.
pub struct JsonStoreDef {
    name: Ident,
    fields: Vec<JsonStoreField>,
}

impl Parse for JsonStoreDef {
    fn parse(input: ParseStream) -> SynResult<Self> {
        // Parse: Name { fields... }
        let name: Ident = input.parse()?;

        // Parse braced field list
        let content;
        syn::braced!(content in input);

        let mut fields = Vec::new();
        while !content.is_empty() {
            let field_name: Ident = content.parse()?;
            content.parse::<Token![:]>()?;
            let field_type: Type = content.parse()?;

            let default = if content.peek(Token![=]) {
                content.parse::<Token![=]>()?;
                let expr: Expr = content.parse()?;
                Some(expr)
            } else {
                None
            };

            // Optional trailing comma
            if content.peek(Token![,]) {
                content.parse::<Token![,]>()?;
            }

            fields.push(JsonStoreField {
                name: field_name,
                ty: field_type,
                default,
            });
        }

        Ok(JsonStoreDef { name, fields })
    }
}

/// Extract the inner type T from Option<T>.
/// Returns None if the type is not Option<T>.
fn extract_option_inner(ty: &Type) -> Option<&Type> {
    if let Type::Path(type_path) = ty {
        let seg = type_path.path.segments.last()?;
        if seg.ident == "Option" {
            if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
                if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {
                    return Some(inner);
                }
            }
        }
    }
    None
}

/// Check if a type's last path segment is "String".
fn is_string_type(ty: &Type) -> bool {
    if let Type::Path(type_path) = ty {
        if let Some(seg) = type_path.path.segments.last() {
            return seg.ident == "String";
        }
    }
    false
}

/// Check if a type is `Option<Vec<...>>`.
fn is_option_vec(ty: &Type) -> bool {
    if let Some(Type::Path(type_path)) = extract_option_inner(ty) {
        if let Some(seg) = type_path.path.segments.last() {
            return seg.ident == "Vec";
        }
    }
    false
}

pub fn generate(def: JsonStoreDef) -> TokenStream {
    let JsonStoreDef { name, fields } = &def;

    // --- struct fields (Option fields get skip_serializing_if) ---
    let struct_fields: Vec<_> = fields.iter().map(|f| {
        let fname = &f.name;
        let ftype = &f.ty;
        if extract_option_inner(ftype).is_some() {
            quote! {
                #[serde(skip_serializing_if = "Option::is_none")]
                pub #fname: #ftype
            }
        } else {
            quote! { pub #fname: #ftype }
        }
    }).collect();

    // --- merge_fallback body ---
    // Option<Vec<T>>: "empty=none" — None or empty Vec both trigger fallback
    // Other Option<T>: standard is_none check
    let merge_stmts: Vec<_> = fields.iter().map(|f| {
        let fname = &f.name;
        if is_option_vec(&f.ty) {
            quote! {
                if self.#fname.as_ref().map_or(true, |v| v.is_empty()) {
                    self.#fname = fallback.#fname.clone();
                }
            }
        } else {
            quote! {
                if self.#fname.is_none() {
                    self.#fname = fallback.#fname.clone();
                }
            }
        }
    }).collect();



    // --- accessor methods for fields with defaults ---
    let accessor_methods: Vec<_> = fields.iter().filter_map(|f| {
        let default_expr = f.default.as_ref()?;
        let fname = &f.name;
        let method_name = format_ident!("{}_or_default", fname);

        // Extract inner type from Option<T>
        let inner_ty = extract_option_inner(&f.ty).unwrap_or(&f.ty);

        if is_string_type(inner_ty) {
            // String: clone + unwrap_or_else(|| default.into())
            Some(quote! {
                pub fn #method_name(&self) -> #inner_ty {
                    self.#fname.clone().unwrap_or_else(|| (#default_expr).into())
                }
            })
        } else {
            // Numeric / other Copy types: unwrap_or(default)
            Some(quote! {
                pub fn #method_name(&self) -> #inner_ty {
                    self.#fname.unwrap_or(#default_expr)
                }
            })
        }
    }).collect();

    // --- defaults_instance: a Settings with all default values filled ---
    let defaults_field_inits: Vec<_> = fields.iter().map(|f| {
        let fname = &f.name;
        if let Some(default_expr) = &f.default {
            let inner_ty = extract_option_inner(&f.ty).unwrap_or(&f.ty);
            if is_string_type(inner_ty) {
                quote! { #fname: Some((#default_expr).into()) }
            } else {
                quote! { #fname: Some(#default_expr) }
            }
        } else {
            quote! { #fname: None }
        }
    }).collect();

    quote! {
        #[derive(Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
        pub struct #name {
            #(#struct_fields,)*
        }

        impl #name {
            /// Load from JSON file. Returns Default if file doesn't exist or parse fails.
            pub fn load(path: impl AsRef<std::path::Path>) -> Self {
                match std::fs::read_to_string(path.as_ref()) {
                    Ok(content) => {
                        serde_json::from_str(&content).unwrap_or_default()
                    }
                    Err(_) => Self::default(),
                }
            }

            /// Save to JSON file with atomic write (write tmp → rename).
            /// Creates parent directories if needed.
            pub fn save(&self, path: impl AsRef<std::path::Path>) -> Result<(), String> {
                let path = path.as_ref();
                if let Some(parent) = path.parent() {
                    std::fs::create_dir_all(parent)
                        .map_err(|e| format!("create dir failed: {}", e))?;
                }
                let json = serde_json::to_string_pretty(self)
                    .map_err(|e| format!("serialize failed: {}", e))?;
                let tmp_path = path.with_extension("tmp");
                std::fs::write(&tmp_path, &json)
                    .map_err(|e| format!("write tmp failed: {}", e))?;
                std::fs::rename(&tmp_path, path)
                    .map_err(|e| format!("rename failed: {}", e))?;
                Ok(())
            }

            /// Merge fallback: for each None field, fill from fallback.
            pub fn merge_fallback(&mut self, fallback: &Self) {
                #(#merge_stmts)*
            }

            /// Return an instance with all declared default values filled.
            pub fn defaults() -> Self {
                Self {
                    #(#defaults_field_inits,)*
                }
            }

            #(#accessor_methods)*
        }
    }
}