use crate::helpers::fn_args_with_types;
use convert_case::{Case, Casing};
use genco::prelude::*;
use rust::Tokens;
use sails_idl_parser::{ast::visitor, ast::visitor::Visitor, ast::*};

pub(crate) struct MockGenerator<'a> {
    service_name: &'a str,
    sails_path: &'a str,
    tokens: rust::Tokens,
}

impl<'a> MockGenerator<'a> {
    pub(crate) fn new(service_name: &'a str, sails_path: &'a str) -> Self {
        Self {
            service_name,
            sails_path,
            tokens: Tokens::new(),
        }
    }

    pub(crate) fn finalize(self) -> Tokens {
        let service_name_snake = &self.service_name.to_case(Case::Snake);
        quote! {
            mock! {
                pub $(self.service_name) {}

                #[allow(refining_impl_trait)]
                #[allow(clippy::type_complexity)]
                impl $service_name_snake::$(self.service_name) for $(self.service_name) {
                    type Env = $(self.sails_path)::client::GstdEnv;
                    $(self.tokens)
                }
            }
        }
    }
}

impl<'ast> Visitor<'ast> for MockGenerator<'_> {
    fn visit_service(&mut self, service: &'ast Service) {
        visitor::accept_service(service, self);
    }

    fn visit_service_func(&mut self, func: &'ast ServiceFunc) {
        let service_name_snake = &self.service_name.to_case(Case::Snake);
        let mutability = if func.is_query() { "" } else { "mut" };
        let fn_name = func.name();
        let fn_name_snake = func.name().to_case(Case::Snake);
        let params_with_types = &fn_args_with_types(func.params());

        quote_in! { self.tokens =>
            fn $fn_name_snake (&$mutability self, $params_with_types) -> $(self.sails_path)::client::PendingCall<$service_name_snake::io::$fn_name, $(self.sails_path)::client::GstdEnv>;
        };
    }
}
