use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse::Parse, parse::ParseStream, punctuated::Punctuated, token, Ident, LitStr, Result, Error, Token, Type};

/// Check if a Type is `Sealed<...>` or `&Sealed<...>`.
/// Returns true if the parameter should be auto-unsealed in i18n! output.
fn is_sealed_type(ty: &Type) -> bool {
    match ty {
        Type::Path(tp) => {
            tp.path.segments.last()
                .map(|seg| seg.ident == "Sealed")
                .unwrap_or(false)
        }
        Type::Reference(tr) => is_sealed_type(&tr.elem),
        _ => false,
    }
}

/// A single i18n entry: `name(arg: Type, ...) = "template";` or `name(arg: Type, ...) = { block };`
struct I18nEntry {
    name: Ident,
    args: Vec<(Ident, Type)>,
    body: EntryBody,
}

enum EntryBody {
    /// String template: `"template {arg}"`
    Template(LitStr),
    /// Code block: `{ expr }`
    Block(proc_macro2::TokenStream),
}

/// The full i18n! input: a sequence of entries
pub(crate) struct I18nInput {
    entries: Vec<I18nEntry>,
}

impl Parse for I18nInput {
    fn parse(input: ParseStream) -> Result<Self> {
        let mut entries = Vec::new();
        while !input.is_empty() {
            // Skip comments (they appear as nothing in token stream)
            entries.push(input.parse::<I18nEntry>()?);
        }
        Ok(I18nInput { entries })
    }
}

impl Parse for I18nEntry {
    fn parse(input: ParseStream) -> Result<Self> {
        let name: Ident = input.parse()?;

        // Parse argument list: (arg: Type, ...)
        let content;
        syn::parenthesized!(content in input);
        let args = parse_args(&content)?;

        // Parse `=`
        input.parse::<Token![=]>()?;

        // Parse body: string literal or block
        let body = if input.peek(LitStr) {
            let lit: LitStr = input.parse()?;
            EntryBody::Template(lit)
        } else if input.peek(token::Brace) {
            let content;
            syn::braced!(content in input);
            let block_tokens: proc_macro2::TokenStream = content.parse()?;
            EntryBody::Block(block_tokens)
        } else {
            return Err(input.error("expected string literal or block after `=`"));
        };

        // Parse trailing `;`
        input.parse::<Token![;]>()?;

        Ok(I18nEntry { name, args, body })
    }
}

fn parse_args(input: ParseStream) -> Result<Vec<(Ident, Type)>> {
    if input.is_empty() {
        return Ok(Vec::new());
    }
    let punctuated: Punctuated<(Ident, Type), Token![,]> =
        Punctuated::parse_terminated_with(input, |input| {
            let name: Ident = input.parse()?;
            input.parse::<Token![:]>()?;
            let ty: Type = input.parse()?;
            Ok((name, ty))
        })?;
    Ok(punctuated.into_iter().collect())
}

/// Extract placeholder names from a format template string.
/// Handles `{name}` and `{name:spec}` (e.g. `{error:#}`).
/// Returns the name part only.
fn extract_placeholders(template: &str) -> Vec<String> {
    let mut names = Vec::new();
    let mut chars = template.chars().peekable();
    while let Some(ch) = chars.next() {
        if ch == '{' {
            // Check for escaped `{{`
            if chars.peek() == Some(&'{') {
                chars.next();
                continue;
            }
            let mut name = String::new();
            for c in chars.by_ref() {
                if c == '}' || c == ':' {
                    break;
                }
                name.push(c);
            }
            if !name.is_empty() {
                // If we stopped at ':', consume until '}'
                // (already consumed by the loop above if it hit '}')
                names.push(name);
            }
        } else if ch == '}' {
            // Check for escaped `}}`
            if chars.peek() == Some(&'}') {
                chars.next();
            }
        }
    }
    names
}

/// Validate that all placeholders in the template match argument names.
fn validate_template(lit: &LitStr, arg_names: &[String]) -> Result<()> {
    let template = lit.value();
    let placeholders = extract_placeholders(&template);
    for ph in &placeholders {
        if !arg_names.iter().any(|a| a == ph) {
            return Err(Error::new(
                lit.span(),
                format!(
                    "placeholder '{{{ph}}}' does not match any argument. Available: [{}]",
                    arg_names.join(", ")
                ),
            ));
        }
    }
    Ok(())
}

pub(crate) fn expand_i18n(input: I18nInput) -> TokenStream {
    let mut functions = Vec::new();

    for entry in &input.entries {
        let name = &entry.name;
        let arg_names: Vec<_> = entry.args.iter().map(|(n, _)| n).collect();
        let arg_types: Vec<_> = entry.args.iter().map(|(_, t)| t).collect();

        // Generate auto-unseal bindings for Sealed parameters
        let unseal_bindings: Vec<_> = entry.args.iter()
            .filter(|(_, ty)| is_sealed_type(ty))
            .map(|(name, _)| {
                quote! { let #name = #name.unseal(); }
            })
            .collect();

        let body_tokens = match &entry.body {
            EntryBody::Template(lit) => {
                // Validate placeholders at compile time
                let arg_name_strings: Vec<String> =
                    entry.args.iter().map(|(n, _)| n.to_string()).collect();
                if let Err(e) = validate_template(lit, &arg_name_strings) {
                    return e.to_compile_error();
                }
                let template_str = lit.value();
                let template_lit = LitStr::new(&template_str, lit.span());
                quote! { { #(#unseal_bindings)* format!(#template_lit, #(#arg_names = #arg_names),*) } }
            }
            EntryBody::Block(block_tokens) => {
                quote! { { #(#unseal_bindings)* #block_tokens } }
            }
        };

        functions.push(quote! {
            #[allow(dead_code)]
            pub fn #name(#(#arg_names : #arg_types),*) -> mad_hatter::Sealed<String> {
                mad_hatter::Sealed::new(#body_tokens)
            }
        });
    }

    quote! { #(#functions)* }
}

pub fn i18n_macro(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
    let parsed = match syn::parse2::<I18nInput>(input) {
        Ok(v) => v,
        Err(e) => return e.to_compile_error(),
    };
    expand_i18n(parsed)
}

/// Recursively check if a Type contains `Sealed` anywhere in its structure.
fn type_contains_sealed(ty: &Type) -> bool {
    match ty {
        Type::Path(tp) => {
            for seg in &tp.path.segments {
                if seg.ident == "Sealed" {
                    return true;
                }
                // Check generic arguments (e.g. Option<Sealed<String>>)
                if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
                    for arg in &args.args {
                        if let syn::GenericArgument::Type(inner_ty) = arg {
                            if type_contains_sealed(inner_ty) {
                                return true;
                            }
                        }
                    }
                }
            }
            false
        }
        Type::Reference(tr) => type_contains_sealed(&tr.elem),
        Type::Tuple(tt) => tt.elems.iter().any(type_contains_sealed),
        Type::Paren(tp) => type_contains_sealed(&tp.elem),
        _ => false,
    }
}

/// Attribute macro `#[i18n]` — compile-time check that function returns Sealed.
///
/// Marks a function as an i18n message producer. The function must return
/// a type containing `Sealed` (e.g. `Sealed<String>`, `Option<Sealed<String>>`).
/// Compile error if return type does not contain `Sealed`.
/// Does not alter runtime behavior — pure compile-time marker.
pub fn i18n_attr(_attr: proc_macro2::TokenStream, item: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
    let func = match syn::parse2::<syn::ItemFn>(item.clone()) {
        Ok(f) => f,
        Err(e) => return e.to_compile_error(),
    };

    // Check return type contains Sealed
    let has_sealed = match &func.sig.output {
        syn::ReturnType::Default => false,
        syn::ReturnType::Type(_, ty) => type_contains_sealed(ty),
    };

    if !has_sealed {
        return syn::Error::new_spanned(
            &func.sig,
            "#[i18n] function must return a type containing Sealed (e.g. Sealed<String>, Option<Sealed<String>>)",
        ).to_compile_error();
    }

    // Pass through unchanged
    item
}