use crate::renderer::field_renderer::quote_fields;
use crate::renderer::type_renderer::quote_type;
use crate::renderer::{quote_escaped_ident, to_module_path, IsRenderable, RenderContext};
use daml_lf::element::{DamlChoice, DamlEnum, DamlField, DamlRecord, DamlTemplate, DamlType, DamlVariant};
use heck::ToSnakeCase;
use proc_macro2::TokenStream;
use quote::quote;

pub fn quote_daml_template(ctx: &RenderContext<'_>, template: &DamlTemplate<'_>) -> TokenStream {
    let package_id = template.package_id();
    let module_name = to_module_path(template.module_path());
    let name_tokens = quote_escaped_ident(template.name());
    let supported_fields: Vec<_> =
        template.fields().iter().filter(|&field| IsRenderable::new(ctx).check_type(field.ty())).collect();
    let all_fields_tokens = quote_fields(supported_fields.as_slice());
    let all_choices_tokens: Vec<_> = template.choices().iter().map(|c| quote_choice(ctx, c)).collect();
    quote!(
        #[DamlTemplate(package_id = #package_id, module_name = #module_name)]
        pub struct #name_tokens {
            #all_fields_tokens
        }
        #[DamlChoices]
        impl #name_tokens {
            #( #all_choices_tokens )*
        }
    )
}

fn quote_choice(ctx: &RenderContext<'_>, choice: &DamlChoice<'_>) -> TokenStream {
    let choice_name_tokens = quote_escaped_ident(choice.name());
    let function_name_tokens = quote_escaped_ident(choice.name().to_snake_case());
    let supported_fields: Vec<_> =
        choice.fields().iter().filter(|&field| IsRenderable::new(ctx).check_type(field.ty())).collect();
    let arg_tokens = quote_fields(supported_fields.as_slice());
    quote!(
        #[#choice_name_tokens]
        pub fn #function_name_tokens(&self, #arg_tokens) {}
    )
}

pub fn quote_daml_record(ctx: &RenderContext<'_>, record: &DamlRecord<'_>) -> TokenStream {
    let name_tokens = quote_escaped_ident(&record.name());
    let supported_fields: Vec<_> =
        record.fields().iter().filter(|&field| IsRenderable::new(ctx).check_type(field.ty())).collect();
    let all_fields_tokens = quote_fields(supported_fields.as_slice());
    quote!(
        #[DamlData]
        pub struct #name_tokens {
            #all_fields_tokens
        }
    )
}

pub fn quote_daml_variant(ctx: &RenderContext<'_>, variant: &DamlVariant<'_>) -> TokenStream {
    let name_tokens = quote_escaped_ident(variant.name());
    let all_variants_tokens: Vec<_> = variant
        .fields()
        .iter()
        .filter_map(|field| IsRenderable::new(ctx).check_type(field.ty()).then(|| quote_variant_field(field)))
        .collect();
    quote!(
        #[DamlVariant]
        pub enum #name_tokens {
            #( #all_variants_tokens ),*
        }
    )
}

fn quote_variant_field(field: &DamlField<'_>) -> TokenStream {
    let name_tokens = quote_escaped_ident(field.name());
    if let DamlType::Unit = field.ty() {
        quote!(
            #name_tokens
        )
    } else {
        let type_tokens = quote_type(field.ty());
        quote!(
            #name_tokens (#type_tokens)
        )
    }
}

pub fn quote_daml_enum(_ctx: &RenderContext<'_>, data_enum: &DamlEnum<'_>) -> TokenStream {
    let name_tokens = quote_escaped_ident(data_enum.name());
    let all_enum_constructors: Vec<_> = data_enum.constructors().map(quote_enum_field).collect();
    quote!(
        #[DamlEnum]
        pub enum #name_tokens {
            #( #all_enum_constructors ),*
        }
    )
}

fn quote_enum_field(field: &str) -> TokenStream {
    let name = quote_escaped_ident(field);
    quote!(
        #name
    )
}
