// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use proc_macro2::Span;
use syn::ext::IdentExt as _;
use syn::{Ident, Lifetime, Type};

pub fn static_lt() -> Lifetime {
    Lifetime::new("'static", Span::call_site())
}

pub fn custom_lt(s: &str) -> Lifetime {
    Lifetime::new(s, Span::call_site())
}

/// The returned ident should not be used in a way that impacts the code generated by the derive.
pub fn ignored_lifetime_ident() -> Ident {
    Ident::new("_does_not_matter", Span::call_site())
}

/// `lt_param` is the un-raw version of the lifetime parameter of the yokeable type.
/// This function replaces all (possibly raw) occurrences of `lt_param` in the type `x` with the
/// `lt` lifetime.
pub fn replace_lifetime(lt_param: &Ident, x: &Type, lt: Lifetime) -> Type {
    use syn::fold::Fold;
    struct ReplaceLifetime<'a>(&'a Ident, Lifetime);

    impl Fold for ReplaceLifetime<'_> {
        fn fold_lifetime(&mut self, lt: Lifetime) -> Lifetime {
            if lt.ident.unraw() == *self.0 {
                // Note that `for<>` binders cannot introduce a lifetime already in scope,
                // so `lt.ident` necessarily refers to the lifetime parameter of the yokeable.
                self.1.clone()
            } else {
                lt
            }
        }
    }
    ReplaceLifetime(lt_param, lt).fold_type(x.clone())
}

#[cfg(test)]
mod tests {
    use proc_macro2::Span;
    use syn::{parse_quote, Ident};

    use super::{custom_lt, replace_lifetime};

    fn a_ident() -> Ident {
        Ident::new("a", Span::call_site())
    }

    fn b_ident() -> Ident {
        Ident::new("b", Span::call_site())
    }

    #[test]
    fn replace_lt_static() {
        assert_eq!(
            replace_lifetime(&a_ident(), &parse_quote!(Foo<'static, T>), custom_lt("'a")),
            parse_quote!(Foo<'static, T>),
        );

        assert_eq!(
            replace_lifetime(&a_ident(), &parse_quote!(Foo<'a, T>), custom_lt("'static")),
            parse_quote!(Foo<'static, T>),
        );
    }

    #[test]
    fn replace_lt_ab() {
        // Probably shouldn't happen in actual use cases, but still works
        assert_eq!(
            replace_lifetime(&a_ident(), &parse_quote!(Foo<'a, 'b, T>), custom_lt("'c")),
            parse_quote!(Foo<'c, 'b, T>),
        );

        assert_eq!(
            replace_lifetime(
                &b_ident(),
                &parse_quote!(for<'a> fn(fn(Foo<'a, 'b, T>))),
                custom_lt("'c"),
            ),
            parse_quote!(for<'a> fn(fn(Foo<'a, 'c, T>))),
        );
    }

    #[test]
    fn replace_lt_macro() {
        assert_eq!(
            replace_lifetime(&a_ident(), &parse_quote!(foo!(Foo<'a, T>)), custom_lt("'b")),
            parse_quote!(foo!(Foo<'a, T>)),
        );

        assert_eq!(
            replace_lifetime(&a_ident(), &parse_quote!(Foo<'a, foo!(T)>), custom_lt("'b")),
            parse_quote!(Foo<'b, foo!(T)>),
        );

        assert_eq!(
            replace_lifetime(&a_ident(), &parse_quote!(Foo<foo!('a), T>), custom_lt("'b")),
            parse_quote!(Foo<foo!('a), T>),
        );
    }

    #[test]
    fn replace_raw() {
        assert_eq!(
            replace_lifetime(&a_ident(), &parse_quote!(Foo<'r#a, T>), custom_lt("'b")),
            parse_quote!(Foo<'b, T>),
        );
    }
}
