//! FormatStruct derive macro — unified Display + FromStr + Serde for structs.
//!
//! ```ignore
//! #[derive(FormatStruct)]
//! #[format("{provider}@{model_id}")]
//! pub struct ModelRef {
//!     pub provider: Option<String>,
//!     pub model_id: String,
//! }
//! ```

use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Fields, Type};

/// A segment in the format string: field name + separator after it.
struct Segment {
    field_name: String,
    separator: String,
}

/// Parse a format string like `"{provider}@{model_id}"` into segments.
#[allow(clippy::string_slice)]
fn parse_format_string(fmt: &str) -> Result<Vec<Segment>, String> {
    let mut segments = Vec::new();
    let mut rest = fmt;

    while !rest.is_empty() {
        // Expect '{'
        if !rest.starts_with('{') {
            return Err(format!(
                "expected '{{' at position {}, found '{}'",
                fmt.len() - rest.len(),
                &rest[..1]
            ));
        }
        rest = &rest[1..];

        // Find closing '}'
        let close = rest.find('}').ok_or_else(|| "unclosed '{'".to_owned())?;
        let field_name = rest[..close].to_owned();
        rest = &rest[close + 1..];

        // Everything until the next '{' (or end) is the separator
        let sep_end = rest.find('{').unwrap_or(rest.len());
        let separator = rest[..sep_end].to_owned();
        rest = &rest[sep_end..];

        segments.push(Segment {
            field_name,
            separator,
        });
    }

    if segments.is_empty() {
        return Err("format string has no fields".to_owned());
    }

    Ok(segments)
}

/// Check if a type is `Option<T>` and return the inner type if so.
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 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
}

/// Extract `#[format("...")]` attribute value.
fn extract_format_attr(input: &DeriveInput) -> Result<String, syn::Error> {
    for attr in &input.attrs {
        if attr.path().is_ident("format") {
            let lit: syn::LitStr = attr.parse_args()?;
            return Ok(lit.value());
        }
    }
    Err(syn::Error::new_spanned(
        &input.ident,
        "FormatStruct requires #[format(\"...\")] attribute",
    ))
}

pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = syn::parse_macro_input!(input as DeriveInput);
    match derive_inner(&input) {
        Ok(tokens) => tokens.into(),
        Err(e) => e.to_compile_error().into(),
    }
}

fn derive_inner(input: &DeriveInput) -> Result<TokenStream, syn::Error> {
    let struct_name = &input.ident;

    let fields = match &input.data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(named) => &named.named,
            _ => {
                return Err(syn::Error::new_spanned(
                    struct_name,
                    "FormatStruct only supports named fields",
                ));
            }
        },
        _ => {
            return Err(syn::Error::new_spanned(
                struct_name,
                "FormatStruct can only be derived for structs",
            ));
        }
    };

    let fmt_str = extract_format_attr(input)?;
    let segments = parse_format_string(&fmt_str).map_err(|e| {
        syn::Error::new_spanned(struct_name, format!("invalid format string: {}", e))
    })?;

    // Validate: each segment field name must match a struct field
    let field_map: Vec<_> = fields
        .iter()
        .map(|f| {
            let name = f.ident.as_ref().unwrap().to_string();
            (name, f)
        })
        .collect();

    for seg in &segments {
        if !field_map.iter().any(|(name, _)| name == &seg.field_name) {
            return Err(syn::Error::new_spanned(
                struct_name,
                format!(
                    "format field '{}' not found in struct fields",
                    seg.field_name
                ),
            ));
        }
    }

    // Build Display impl
    let display_body = gen_display(&segments, &field_map);

    // Build From<String> impl
    let from_string_body = gen_from_string(struct_name, &segments, &field_map)?;

    let output = quote! {
        impl ::std::fmt::Display for #struct_name {
            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
                #display_body
                Ok(())
            }
        }

        impl ::std::str::FromStr for #struct_name {
            type Err = ::std::convert::Infallible;

            fn from_str(s: &str) -> Result<Self, Self::Err> {
                Ok(Self::from(s.to_owned()))
            }
        }

        impl From<String> for #struct_name {
            fn from(s: String) -> Self {
                #from_string_body
            }
        }

        impl From<&str> for #struct_name {
            fn from(s: &str) -> Self {
                Self::from(s.to_owned())
            }
        }

        impl From<#struct_name> for String {
            fn from(v: #struct_name) -> Self {
                v.to_string()
            }
        }

        impl ::serde::Serialize for #struct_name {
            fn serialize<S: ::serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
                serializer.serialize_str(&self.to_string())
            }
        }

        impl<'de> ::serde::Deserialize<'de> for #struct_name {
            fn deserialize<D: ::serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
                let s = <String as ::serde::Deserialize>::deserialize(deserializer)?;
                Ok(Self::from(s))
            }
        }
    };

    // ── sqlx Type ──
    let sqlx_type = quote! {
        impl ::mad_hatter::__sqlx::Type<::mad_hatter::__sqlx::sqlite::Sqlite> for #struct_name {
            fn type_info() -> ::mad_hatter::__sqlx::sqlite::SqliteTypeInfo {
                <String as ::mad_hatter::__sqlx::Type<::mad_hatter::__sqlx::sqlite::Sqlite>>::type_info()
            }
        }
    };

    // ── sqlx Encode ──
    let sqlx_encode = quote! {
        impl<'__q> ::mad_hatter::__sqlx::Encode<'__q, ::mad_hatter::__sqlx::sqlite::Sqlite> for #struct_name {
            fn encode_by_ref(
                &self,
                args: &mut Vec<::mad_hatter::__sqlx::sqlite::SqliteArgumentValue<'__q>>,
            ) -> Result<::mad_hatter::__sqlx::encode::IsNull, ::mad_hatter::__sqlx::error::BoxDynError> {
                <String as ::mad_hatter::__sqlx::Encode<'__q, ::mad_hatter::__sqlx::sqlite::Sqlite>>::encode(
                    self.to_string(), args
                )
            }
        }
    };

    // ── sqlx Decode ──
    let sqlx_decode = quote! {
        impl<'__r> ::mad_hatter::__sqlx::Decode<'__r, ::mad_hatter::__sqlx::sqlite::Sqlite> for #struct_name {
            fn decode(
                value: ::mad_hatter::__sqlx::sqlite::SqliteValueRef<'__r>,
            ) -> Result<Self, ::mad_hatter::__sqlx::error::BoxDynError> {
                let s = <String as ::mad_hatter::__sqlx::Decode<'__r, ::mad_hatter::__sqlx::sqlite::Sqlite>>::decode(value)?;
                Ok(Self::from(s))
            }
        }
    };

    let output = quote! {
        #output

        #sqlx_type
        #sqlx_encode
        #sqlx_decode
    };

    Ok(output)
}

fn gen_display(
    segments: &[Segment],
    field_map: &[(String, &syn::Field)],
) -> TokenStream {
    let mut stmts = Vec::new();

    for seg in segments {
        let field_ident = syn::Ident::new(&seg.field_name, proc_macro2::Span::call_site());
        let (_, field) = field_map
            .iter()
            .find(|(name, _)| name == &seg.field_name)
            .unwrap();
        let sep = &seg.separator;
        let is_option = extract_option_inner(&field.ty).is_some();

        if is_option {
            // Option field: if Some, write value + separator; if None, skip both
            if sep.is_empty() {
                stmts.push(quote! {
                    if let Some(ref val) = self.#field_ident {
                        ::std::fmt::Display::fmt(val, f)?;
                    }
                });
            } else {
                stmts.push(quote! {
                    if let Some(ref val) = self.#field_ident {
                        ::std::fmt::Display::fmt(val, f)?;
                        f.write_str(#sep)?;
                    }
                });
            }
        } else {
            // Required field: always write value + separator
            if sep.is_empty() {
                stmts.push(quote! {
                    ::std::fmt::Display::fmt(&self.#field_ident, f)?;
                });
            } else {
                stmts.push(quote! {
                    ::std::fmt::Display::fmt(&self.#field_ident, f)?;
                    f.write_str(#sep)?;
                });
            }
        }
    }

    quote! { #(#stmts)* }
}

fn gen_from_string(
    struct_name: &syn::Ident,
    segments: &[Segment],
    field_map: &[(String, &syn::Field)],
) -> Result<TokenStream, syn::Error> {
    // Strategy:
    // - Walk segments in order
    // - For each non-last segment, try split_once on the separator
    // - Option field + no separator found → None
    // - Required field + no separator found → use remaining, rest defaults
    // - Last segment always gets the remaining string

    let mut stmts = Vec::new();

    // We'll use a mutable `rest` variable to track remaining input
    stmts.push(quote! { let mut rest: &str = s.as_str(); });

    let num_segments = segments.len();

    for (i, seg) in segments.iter().enumerate() {
        let field_ident = syn::Ident::new(&seg.field_name, proc_macro2::Span::call_site());
        let (_, field) = field_map
            .iter()
            .find(|(name, _)| name == &seg.field_name)
            .unwrap();

        let is_option = extract_option_inner(&field.ty).is_some();
        let inner_ty = extract_option_inner(&field.ty).unwrap_or(&field.ty);
        let is_str = is_string_type(inner_ty);
        let is_last = i == num_segments - 1;

        if is_last {
            // Last field: takes the remaining string
            if is_option {
                if is_str {
                    stmts.push(quote! {
                        let #field_ident = if rest.is_empty() { None } else { Some(rest.to_owned()) };
                    });
                } else {
                    stmts.push(quote! {
                        let #field_ident = if rest.is_empty() {
                            None
                        } else {
                            rest.parse().ok()
                        };
                    });
                }
            } else if is_str {
                stmts.push(quote! {
                    let #field_ident = rest.to_owned();
                });
            } else {
                stmts.push(quote! {
                    let #field_ident = rest.parse().unwrap_or_default();
                });
            }
        } else {
            // Non-last field: split on separator
            let sep = &seg.separator;

            if is_option {
                // Option field: if separator found, take the left part; else None
                if is_str {
                    stmts.push(quote! {
                        let #field_ident = if let Some(pos) = rest.find(#sep) {
                            let val = rest[..pos].to_owned();
                            rest = &rest[pos + #sep.len()..];
                            Some(val)
                        } else {
                            None
                        };
                    });
                } else {
                    stmts.push(quote! {
                        let #field_ident = if let Some(pos) = rest.find(#sep) {
                            let val = rest[..pos].parse().ok();
                            rest = &rest[pos + #sep.len()..];
                            val
                        } else {
                            None
                        };
                    });
                }
            } else {
                // Required field: split on separator, or take all remaining
                if is_str {
                    stmts.push(quote! {
                        let #field_ident = if let Some(pos) = rest.find(#sep) {
                            let val = rest[..pos].to_owned();
                            rest = &rest[pos + #sep.len()..];
                            val
                        } else {
                            let val = rest.to_owned();
                            rest = "";
                            val
                        };
                    });
                } else {
                    stmts.push(quote! {
                        let #field_ident = if let Some(pos) = rest.find(#sep) {
                            let val = rest[..pos].parse().unwrap_or_default();
                            rest = &rest[pos + #sep.len()..];
                            val
                        } else {
                            let val = rest.parse().unwrap_or_default();
                            rest = "";
                            val
                        };
                    });
                }
            }
        }
    }

    // Build the struct construction
    let field_names: Vec<_> = segments
        .iter()
        .map(|s| syn::Ident::new(&s.field_name, proc_macro2::Span::call_site()))
        .collect();

    stmts.push(quote! {
        #struct_name { #(#field_names),* }
    });

    Ok(quote! { #(#stmts)* })
}