use indent::indent_by;
use indoc::formatdoc;
use itertools::Itertools;

use super::PluginTypeInfo;
use crate::plugins::utils::TypeVariant;

/// Adds derive result for the `Serde` trait.
pub fn handle_serde(info: &PluginTypeInfo<'_>, member_access_desnaps: bool) -> String {
    const SERDE_TRAIT: &str = "core::serde::Serde";
    const DESTRUCT_TRAIT: &str = "core::traits::Destruct";
    let ty = &info.name;
    let full_typename = info.full_typename();
    let header = match &info.type_variant {
        TypeVariant::Enum => info.impl_header(SERDE_TRAIT, &[SERDE_TRAIT]),
        TypeVariant::Struct => info.impl_header(SERDE_TRAIT, &[SERDE_TRAIT, DESTRUCT_TRAIT]),
    };
    let serialize_body = indent_by(
        8,
        match &info.type_variant {
            TypeVariant::Enum => {
                formatdoc! {"
                match self {{
                    {}
                }}",
                info.members_info.iter().enumerate().map(|(idx, variant)| {
                    format!(
                        "{ty}::{variant}(x) => {{ {SERDE_TRAIT}::<felt252>::serialize(@{idx}, ref output); \
                        {imp}::serialize(x, ref output); }},",
                        variant=variant.name,
                        imp=variant.impl_name(SERDE_TRAIT),
                    )
                }).join("\n    ")}
            }
            TypeVariant::Struct => info
                .members_info
                .iter()
                .map(|member| {
                    let imp = member.impl_name(SERDE_TRAIT);
                    let member = &member.name;
                    if member_access_desnaps {
                        format!("{imp}::serialize(@self.{member}, ref output)")
                    } else {
                        format!("{imp}::serialize(self.{member}, ref output)")
                    }
                })
                .join(";\n"),
        },
    );
    let deserialize_body = indent_by(
        8,
        match &info.type_variant {
            TypeVariant::Enum => {
                formatdoc! {"
                    let idx: felt252 = {SERDE_TRAIT}::<felt252>::deserialize(ref serialized)?;
                    core::option::Option::Some(
                        match idx {{
                            {}
                            _ => {{ return core::option::Option::None; }}
                        }}
                    )",
                    info.members_info.iter().enumerate().map(|(idx, variant)| {
                        format!(
                            "{idx} => {ty}::{variant}(\
                                {imp}::deserialize(ref serialized)?),",
                            variant=variant.name,
                            imp=variant.impl_name(SERDE_TRAIT),
                        )
                    }).join("\n        ")
                }
            }
            TypeVariant::Struct => {
                formatdoc! {"
                    {}
                    core::option::Option::Some({ty} {{
                        {}
                    }})",
                    info.members_info.iter().map(|member|format!(
                        "let __serde_member_{member} = {destruct_with} {{ value: {imp}::deserialize(ref serialized)? }};",
                        member=member.name,
                        destruct_with=member.destruct_with(),
                        imp=member.impl_name(SERDE_TRAIT),
                    )).join("\n"),
                    info.members_info.iter().map(|member|format!(
                        "{member}: __serde_member_{member}.value,", member=member.name
                    )).join("\n    "),
                }
            }
        },
    );
    formatdoc! {"
        {header} {{
            fn serialize(self: @{full_typename}, ref output: core::array::Array<felt252>) {{
                {serialize_body}
            }}
            fn deserialize(ref serialized: core::array::Span<felt252>) -> core::option::Option<{full_typename}> {{
                {deserialize_body}
            }}
        }}
    "}
}
