use crate::{
    ir::{FnArgIR, FnIR},
    utils
};
use syn::token::Pub;
use syn::{parse_quote, Attribute, FnArg};

pub fn runtime_args_block<F: FnMut(&FnArgIR) -> syn::Stmt>(
    fun: &FnIR,
    insert_arg_fn: F
) -> syn::Block {
    let runtime_args = utils::expr::new_runtime_args();
    let args = utils::ident::named_args();
    let insert_args = insert_args_stmts(fun, insert_arg_fn);

    syn::parse_quote!({
        let mut #args = #runtime_args;
        #(#insert_args)*
        #args
    })
}

pub fn insert_args_stmts<F: FnMut(&FnArgIR) -> syn::Stmt>(
    fun: &FnIR,
    insert_arg_fn: F
) -> Vec<syn::Stmt> {
    fun.named_args()
        .iter()
        .map(insert_arg_fn)
        .collect::<Vec<_>>()
}

#[derive(syn_derive::ToTokens)]
pub struct FnItem {
    comment: Option<Attribute>,
    vis_token: Option<syn::Token![pub]>,
    fn_token: syn::Token![fn],
    fn_name: syn::Ident,
    #[syn(parenthesized)]
    paren_token: syn::token::Paren,
    #[syn(in = paren_token)]
    args: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]>,
    ret_ty: syn::ReturnType,
    block: syn::Block
}

impl FnItem {
    pub fn new(
        name: &syn::Ident,
        args: Vec<syn::FnArg>,
        ret_ty: syn::ReturnType,
        block: syn::Block
    ) -> Self {
        Self {
            vis_token: None,
            comment: None,
            fn_token: Default::default(),
            fn_name: name.clone(),
            paren_token: Default::default(),
            args: syn::punctuated::Punctuated::from_iter(args),
            ret_ty,
            block
        }
    }

    pub fn _public(mut self, comment: String) -> FnItem {
        let comment = format!(" {}", comment);
        self.comment = Some(parse_quote!(#[doc = #comment]));
        self.vis_token = Some(Pub::default());
        self
    }

    pub fn instanced(mut self) -> FnItem {
        self.args.insert(0, parse_quote!(&self));
        self
    }
}

#[derive(syn_derive::ToTokens)]
pub struct SingleArgFnItem {
    fn_item: FnItem
}

impl SingleArgFnItem {
    pub fn new(name: &syn::Ident, arg: FnArg, ret_ty: syn::ReturnType, block: syn::Block) -> Self {
        Self {
            fn_item: FnItem::new(name, vec![arg], ret_ty, block)
        }
    }

    pub fn _make_pub(mut self, comment: String) -> Self {
        self.fn_item = self.fn_item._public(comment);
        self
    }

    pub fn _make_instanced(mut self) -> Self {
        self.fn_item = self.fn_item.instanced();
        self
    }
}
