use crate::attr::Attribute;
use crate::item::Item;
use alloc::string::String;
use alloc::vec::Vec;

ast_struct! {
    /// A complete file of Rust source code.
    ///
    /// Typically `File` objects are created with [`parse_file`].
    ///
    /// [`parse_file`]: crate::parse_file
    ///
    /// # Example
    ///
    /// Parse a Rust source file into a `syn::File` and print out a debug
    /// representation of the syntax tree.
    ///
    /// ```
    /// use std::env;
    /// use std::fs;
    /// use std::process;
    ///
    /// fn main() {
    /// # }
    /// #
    /// # fn fake_main() {
    ///     let mut args = env::args();
    ///     let _ = args.next(); // executable name
    ///
    ///     let filename = match (args.next(), args.next()) {
    ///         (Some(filename), None) => filename,
    ///         _ => {
    ///             eprintln!("Usage: dump-syntax path/to/filename.rs");
    ///             process::exit(1);
    ///         }
    ///     };
    ///
    ///     let src = fs::read_to_string(&filename).expect("unable to read file");
    ///     let syntax = syn::parse_file(&src).expect("unable to parse file");
    ///
    ///     // Debug impl is available if Syn is built with "extra-traits" feature.
    ///     println!("{:#?}", syntax);
    /// }
    /// ```
    ///
    /// Running with its own source code as input, this program prints output
    /// that begins with:
    ///
    /// ```text
    /// File {
    ///     shebang: None,
    ///     frontmatter: None,
    ///     attrs: [],
    ///     items: [
    ///         Item::Use {
    ///             attrs: [],
    ///             vis: Visibility::Inherited,
    ///             use_token: Token![use],
    ///             leading_colon: None,
    ///             tree: UseTree::Path(
    ///                 UsePath {
    ///                     ident: Ident(
    ///                         std,
    ///                     ),
    ///                     colon2_token: Token![::],
    ///                     tree: UseTree::Name(
    ///                         UseName {
    ///                             ident: Ident(
    ///                                 env,
    ///                             ),
    ///                         },
    ///                     ),
    ///                 },
    ///             ),
    ///             semi_token: Token![;],
    ///         },
    /// ...
    /// ```
    #[cfg_attr(docsrs, doc(cfg(feature = "full")))]
    pub struct File {
        pub shebang: Option<String>,
        pub frontmatter: Option<Frontmatter>,
        pub attrs: Vec<Attribute>,
        pub items: Vec<Item>,
    }
}

ast_struct! {
    /// A frontmatter section fenced by `---`.
    #[cfg_attr(docsrs, doc(cfg(feature = "full")))]
    #[non_exhaustive]
    pub struct Frontmatter {}
}

#[cfg(feature = "parsing")]
pub(crate) mod parsing {
    use crate::attr::Attribute;
    use crate::error::Result;
    use crate::file::File;
    use crate::parse::{Parse, ParseStream};
    use alloc::vec::Vec;

    #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))]
    impl Parse for File {
        fn parse(input: ParseStream) -> Result<Self> {
            Ok(File {
                shebang: None,
                frontmatter: None,
                attrs: input.call(Attribute::parse_inner)?,
                items: {
                    let mut items = Vec::new();
                    while !input.is_empty() {
                        items.push(input.parse()?);
                    }
                    items
                },
            })
        }
    }
}

#[cfg(feature = "printing")]
mod printing {
    use crate::attr::FilterAttrs;
    use crate::file::File;
    use proc_macro2::TokenStream;
    use quote::{ToTokens, TokenStreamExt as _};

    #[cfg_attr(docsrs, doc(cfg(feature = "printing")))]
    impl ToTokens for File {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            tokens.append_all(self.attrs.inner());
            tokens.append_all(&self.items);
        }
    }
}
