use proc_macro2::TokenStream;
use quote::quote;
use std::collections::{HashMap, HashSet};
use syn::{Ident, TypePath, Visibility, punctuated::Punctuated, token::Comma};

#[derive(Default)]
pub struct EntityLoaderSchema {
    pub fields: Vec<EntityLoaderField>,
}

pub enum EntityLoaderFieldKind {
    HasOne,
    HasOneSelf,
    HasMany,
    HasManySelf,
    ManyToMany,
    ManyToManySelf {
        junction_module: Ident,
        reverse: bool,
    },
}

pub struct EntityLoaderField {
    pub field: Ident,
    /// super::bakery::Entity
    pub entity: TypePath,
    pub relation_enum: Option<syn::LitStr>,
    pub kind: EntityLoaderFieldKind,
}

#[derive(Default)]
struct EntityLoaderOutput {
    loader_with_fields: Punctuated<TokenStream, Comma>,
    loader_nest_fields: Punctuated<TokenStream, Comma>,
    select_tuple_fields: Punctuated<TokenStream, Comma>,
    loader_with_set_impl: TokenStream,
    loader_with_2_impl: TokenStream,
    fetch_select_impl: TokenStream,
    assemble_one: TokenStream,
    load_one: TokenStream,
    load_many: TokenStream,
    load_one_nest: TokenStream,
    load_many_nest: TokenStream,
    load_one_nest_nest: TokenStream,
    load_many_nest_nest: TokenStream,
    with_param_impls: TokenStream,
}

impl EntityLoaderField {
    fn expand_loader_with_field_into(&self, output: &mut EntityLoaderOutput) {
        let field = &self.field;

        output.loader_with_fields.push(quote! {
            #[doc = " Generated by sea-orm-macros"]
            pub #field: bool
        });
    }

    fn expand_loader_nest_field_into(&self, output: &mut EntityLoaderOutput) {
        let field = &self.field;
        let nest_type = match &self.kind {
            EntityLoaderFieldKind::HasOne
            | EntityLoaderFieldKind::HasMany
            | EntityLoaderFieldKind::ManyToMany => {
                let mut entity_module = self.entity.path.clone();
                entity_module.segments.pop();
                entity_module
                    .segments
                    .push(syn::parse_quote!(EntityLoaderWith));
                quote!(#entity_module)
            }
            EntityLoaderFieldKind::HasOneSelf
            | EntityLoaderFieldKind::HasManySelf
            | EntityLoaderFieldKind::ManyToManySelf { .. } => quote!(EntityLoaderWith),
        };

        output.loader_nest_fields.push(quote! {
            #[doc = " Generated by sea-orm-macros"]
            pub #field: #nest_type
        });
    }

    fn expand_relation_tuple_with_param_into(&self, output: &mut EntityLoaderOutput) {
        let mut entity_module = self.entity.path.clone();
        entity_module.segments.pop();
        let mut related_entity = entity_module.clone();
        related_entity.segments.push(syn::parse_quote!(Entity));
        let mut related_relation = entity_module;
        related_relation.segments.push(syn::parse_quote!(Relation));

        output.with_param_impls.extend(quote! {
            impl EntityLoaderWithParam for (Relation, #related_entity) {
                fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option<sea_orm::compound::LoadTarget>) {
                    (
                        sea_orm::compound::LoadTarget::Relation(sea_orm::RelationTrait::name(&self.0)),
                        Some(sea_orm::compound::LoadTarget::TableRef(self.1.table_ref())),
                    )
                }
            }

            impl EntityLoaderWithParam for (Relation, #related_relation) {
                fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option<sea_orm::compound::LoadTarget>) {
                    (
                        sea_orm::compound::LoadTarget::Relation(sea_orm::RelationTrait::name(&self.0)),
                        Some(sea_orm::compound::LoadTarget::Relation(sea_orm::RelationTrait::name(&self.1))),
                    )
                }
            }
        });
    }

    fn expand_many_to_many_self_with_param_into(
        &self,
        output: &mut EntityLoaderOutput,
        junction_module: &Ident,
        reverse: bool,
    ) {
        let target_type = if !reverse {
            Ident::new("TableRef", junction_module.span())
        } else {
            Ident::new("TableRefRev", junction_module.span())
        };

        let target_entity = if !reverse {
            quote!(super::#junction_module::Entity)
        } else {
            quote!(super::#junction_module::EntityReverse)
        };

        output.with_param_impls.extend(quote! {
            impl EntityLoaderWithParam for #target_entity {
                fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option<sea_orm::compound::LoadTarget>) {
                    (sea_orm::compound::LoadTarget::#target_type(super::#junction_module::Entity.table_ref()), None)
                }
            }

            impl<S> EntityLoaderWithParam for (#target_entity, S)
            where
                S: EntityTrait,
                Entity: Related<S>,
            {
                fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option<sea_orm::compound::LoadTarget>) {
                    (
                        sea_orm::compound::LoadTarget::#target_type(super::#junction_module::Entity.table_ref()),
                        Some(sea_orm::compound::LoadTarget::TableRef(self.1.table_ref())),
                    )
                }
            }
        });
    }

    fn expand_loader_with_set_impl_into(
        &self,
        output: &mut EntityLoaderOutput,
        duplicate_entity: bool,
    ) {
        let field = &self.field;
        let entity = &self.entity;

        match &self.kind {
            EntityLoaderFieldKind::HasOne
            | EntityLoaderFieldKind::HasMany
            | EntityLoaderFieldKind::ManyToMany => {
                if !duplicate_entity {
                    output.loader_with_set_impl.extend(quote! {
                        if target == sea_orm::compound::LoadTarget::TableRef(#entity.table_ref()) {
                            self.#field = true;
                        }
                    });
                } else if let Some(relation_enum) = &self.relation_enum {
                    output.loader_with_set_impl.extend(quote! {
                        if let sea_orm::compound::LoadTarget::Relation(relation_enum) = &target {
                            if relation_enum == #relation_enum {
                                self.#field = true;
                            }
                        }
                    });
                }
            }
            EntityLoaderFieldKind::HasOneSelf
            | EntityLoaderFieldKind::HasManySelf
            | EntityLoaderFieldKind::ManyToManySelf { .. } => {
                if let Some(relation_enum) = &self.relation_enum {
                    output.loader_with_set_impl.extend(quote! {
                        if let sea_orm::compound::LoadTarget::Relation(relation_enum) = &target {
                            if relation_enum == #relation_enum {
                                self.#field = true;
                            }
                        }
                    });
                }

                if let EntityLoaderFieldKind::ManyToManySelf {
                    junction_module,
                    reverse,
                } = &self.kind
                {
                    let target_type = if !reverse {
                        Ident::new("TableRef", junction_module.span())
                    } else {
                        Ident::new("TableRefRev", junction_module.span())
                    };

                    output.loader_with_set_impl.extend(quote! {
                        if target == sea_orm::compound::LoadTarget::#target_type(super::#junction_module::Entity.table_ref()) {
                            self.#field = true;
                        }
                    });
                }
            }
        }
    }

    fn expand_loader_with_2_impl_into(
        &self,
        output: &mut EntityLoaderOutput,
        duplicate_entity: bool,
    ) {
        let field = &self.field;
        let entity = &self.entity;

        match &self.kind {
            EntityLoaderFieldKind::HasOne
            | EntityLoaderFieldKind::HasMany
            | EntityLoaderFieldKind::ManyToMany => {
                if !duplicate_entity {
                    output.loader_with_2_impl.extend(quote! {
                        if left == sea_orm::compound::LoadTarget::TableRef(#entity.table_ref()) {
                            self.with.#field = true;
                            self.nest.#field.set(right);
                            return self;
                        }
                    });
                } else if let Some(relation_enum) = &self.relation_enum {
                    output.loader_with_2_impl.extend(quote! {
                        if let sea_orm::compound::LoadTarget::Relation(relation_enum) = &left {
                            if relation_enum == #relation_enum {
                                self.with.#field = true;
                                self.nest.#field.set(right);
                                return self;
                            }
                        }
                    });
                }
            }
            EntityLoaderFieldKind::HasOneSelf | EntityLoaderFieldKind::HasManySelf => {}
            EntityLoaderFieldKind::ManyToManySelf {
                junction_module,
                reverse,
            } => {
                let target_type = if !reverse {
                    Ident::new("TableRef", junction_module.span())
                } else {
                    Ident::new("TableRefRev", junction_module.span())
                };

                output.loader_with_2_impl.extend(quote! {
                    if left == sea_orm::compound::LoadTarget::#target_type(super::#junction_module::Entity.table_ref()) {
                        self.with.#field = true;
                        self.nest.#field.set(right);
                        return self;
                    }
                });
            }
        }
    }

    fn expand_select_one_into(&self, output: &mut EntityLoaderOutput) {
        let field = &self.field;
        let entity = &self.entity;

        output.select_tuple_fields.push(quote!(#field));
        output.fetch_select_impl.extend(quote! {
            let select = if self.with.#field && self.nest.#field.is_empty() {
                self.with.#field = false;
                loaded.#field = true;
                select.find_also(Entity, #entity)
            } else {
                select.select_also_fake(#entity)
            };
        });
        output.assemble_one.extend(quote! {
            if loaded.#field {
                model.#field = #field.map(Into::into).map(Box::new).into();
            }
        });
    }

    fn expand_load_one_into(&self, output: &mut EntityLoaderOutput) {
        let field = &self.field;
        let entity = &self.entity;
        let await_ = if cfg!(feature = "async") {
            quote!(.await)
        } else {
            quote!()
        };
        let mut entity_module = self.entity.path.clone();
        entity_module.segments.pop();
        entity_module.segments.push(syn::parse_quote!(EntityLoader));

        output.load_one.extend(quote! {
            if with.#field {
                let #field = models.as_slice().load_one_ex(#entity, db)#await_?;
                let #field = <#entity_module>::load_nest(#field, &nest.#field, db)#await_?;

                for (model, #field) in models.iter_mut().zip(#field) {
                    model.#field = #field.map(Into::into).map(Box::new).into();
                }
            }
        });
        output.load_one_nest.extend(quote! {
            if with.#field {
                let #field = models.as_slice().load_one_ex(#entity, db)#await_?;

                for (model, #field) in models.iter_mut().zip(#field) {
                    if let Some(model) = model.as_mut() {
                        model.#field = #field.map(Into::into).map(Box::new).into();
                    }
                }
            }
        });
        output.load_one_nest_nest.extend(quote! {
            if with.#field {
                let #field = models.as_slice().load_one_ex(#entity, db)#await_?;

                for (models, #field) in models.iter_mut().zip(#field) {
                    for (model, #field) in models.iter_mut().zip(#field) {
                        model.#field = #field.map(Into::into).map(Box::new).into();
                    }
                }
            }
        });
    }

    fn expand_load_one_with_rel_into(
        &self,
        output: &mut EntityLoaderOutput,
        relation_enum: &syn::LitStr,
    ) {
        let field = &self.field;
        let entity = &self.entity;
        let relation_enum = Ident::new(&relation_enum.value(), relation_enum.span());
        let await_ = if cfg!(feature = "async") {
            quote!(.await)
        } else {
            quote!()
        };
        let mut entity_module = self.entity.path.clone();
        entity_module.segments.pop();
        entity_module.segments.push(syn::parse_quote!(EntityLoader));

        output.load_one.extend(quote! {
            if with.#field {
                let #field = models.as_slice().load_one_ex_with_rel(
                    #entity,
                    sea_orm::RelationTrait::def(&Relation::#relation_enum),
                    db,
                )#await_?;
                let #field = <#entity_module>::load_nest(#field, &nest.#field, db)#await_?;

                for (model, #field) in models.iter_mut().zip(#field) {
                    model.#field = #field.map(Into::into).map(Box::new).into();
                }
            }
        });
        output.load_one_nest.extend(quote! {
            if with.#field {
                let #field = models.as_slice().load_one_ex_with_rel(
                    #entity,
                    sea_orm::RelationTrait::def(&Relation::#relation_enum),
                    db,
                )#await_?;

                for (model, #field) in models.iter_mut().zip(#field) {
                    if let Some(model) = model.as_mut() {
                        model.#field = #field.map(Into::into).map(Box::new).into();
                    }
                }
            }
        });
        output.load_one_nest_nest.extend(quote! {
            if with.#field {
                let #field = models.as_slice().load_one_ex_with_rel(
                    #entity,
                    sea_orm::RelationTrait::def(&Relation::#relation_enum),
                    db,
                )#await_?;

                for (models, #field) in models.iter_mut().zip(#field) {
                    for (model, #field) in models.iter_mut().zip(#field) {
                        model.#field = #field.map(Into::into).map(Box::new).into();
                    }
                }
            }
        });
    }

    fn expand_load_many_into(&self, output: &mut EntityLoaderOutput) {
        let field = &self.field;
        let entity = &self.entity;
        let await_ = if cfg!(feature = "async") {
            quote!(.await)
        } else {
            quote!()
        };
        let mut entity_module = self.entity.path.clone();
        entity_module.segments.pop();
        entity_module.segments.push(syn::parse_quote!(EntityLoader));

        output.load_many.extend(quote! {
            if with.#field {
                let #field = models.as_slice().load_many_ex(#entity, db)#await_?;
                let #field = <#entity_module>::load_nest_nest(#field, &nest.#field, db)#await_?;

                for (model, #field) in models.iter_mut().zip(#field) {
                    model.#field = #field.into();
                }
            }
        });
        output.load_many_nest.extend(quote! {
            if with.#field {
                let #field = models.as_slice().load_many_ex(#entity, db)#await_?;

                for (model, #field) in models.iter_mut().zip(#field) {
                    if let Some(model) = model.as_mut() {
                        model.#field = #field.into();
                    }
                }
            }
        });
        output.load_many_nest_nest.extend(quote! {
            if with.#field {
                let #field = models.as_slice().load_many_ex(#entity, db)#await_?;

                for (models, #field) in models.iter_mut().zip(#field) {
                    for (model, #field) in models.iter_mut().zip(#field) {
                        model.#field = #field.into();
                    }
                }
            }
        });
    }

    fn expand_load_many_with_rel_into(
        &self,
        output: &mut EntityLoaderOutput,
        relation_enum: &syn::LitStr,
    ) {
        let field = &self.field;
        let entity = &self.entity;
        let relation_enum = Ident::new(&relation_enum.value(), relation_enum.span());
        let await_ = if cfg!(feature = "async") {
            quote!(.await)
        } else {
            quote!()
        };
        let mut entity_module = self.entity.path.clone();
        entity_module.segments.pop();
        entity_module.segments.push(syn::parse_quote!(EntityLoader));

        output.load_many.extend(quote! {
            if with.#field {
                let #field = models.as_slice().load_many_ex_with_rel(
                    #entity,
                    sea_orm::RelationTrait::def(&Relation::#relation_enum),
                    db,
                )#await_?;
                let #field = <#entity_module>::load_nest_nest(#field, &nest.#field, db)#await_?;

                for (model, #field) in models.iter_mut().zip(#field) {
                    model.#field = #field.into();
                }
            }
        });
        output.load_many_nest.extend(quote! {
            if with.#field {
                let #field = models.as_slice().load_many_ex_with_rel(
                    #entity,
                    sea_orm::RelationTrait::def(&Relation::#relation_enum),
                    db,
                )#await_?;

                for (model, #field) in models.iter_mut().zip(#field) {
                    if let Some(model) = model.as_mut() {
                        model.#field = #field.into();
                    }
                }
            }
        });
        output.load_many_nest_nest.extend(quote! {
            if with.#field {
                let #field = models.as_slice().load_many_ex_with_rel(
                    #entity,
                    sea_orm::RelationTrait::def(&Relation::#relation_enum),
                    db,
                )#await_?;

                for (models, #field) in models.iter_mut().zip(#field) {
                    for (model, #field) in models.iter_mut().zip(#field) {
                        model.#field = #field.into();
                    }
                }
            }
        });
    }

    fn expand_load_one_self_into(
        &self,
        output: &mut EntityLoaderOutput,
        relation_enum: &syn::LitStr,
    ) {
        let field = &self.field;
        let entity = &self.entity;
        let relation_enum = Ident::new(&relation_enum.value(), relation_enum.span());
        let await_ = if cfg!(feature = "async") {
            quote!(.await)
        } else {
            quote!()
        };

        output.load_one.extend(quote! {
            if with.#field {
                let #field = models.as_slice().load_self_ex(#entity, Relation::#relation_enum, db)#await_?;

                for (model, #field) in models.iter_mut().zip(#field) {
                    model.#field = #field.map(Into::into).map(Box::new).into();
                }
            }
        });
    }

    fn expand_load_many_self_into(
        &self,
        output: &mut EntityLoaderOutput,
        relation_enum: &syn::LitStr,
    ) {
        let field = &self.field;
        let entity = &self.entity;
        let relation_enum = Ident::new(&relation_enum.value(), relation_enum.span());
        let await_ = if cfg!(feature = "async") {
            quote!(.await)
        } else {
            quote!()
        };

        output.load_many.extend(quote! {
            if with.#field {
                let #field = models.as_slice().load_self_many_ex(#entity, Relation::#relation_enum, db)#await_?;

                for (model, #field) in models.iter_mut().zip(#field) {
                    model.#field = #field.into();
                }
            }
        });
    }

    fn expand_load_many_to_many_self_into(
        &self,
        output: &mut EntityLoaderOutput,
        junction_module: &Ident,
        reverse: bool,
    ) {
        let field = &self.field;
        let await_ = if cfg!(feature = "async") {
            quote!(.await)
        } else {
            quote!()
        };

        output.load_many.extend(quote! {
            if with.#field {
                let #field = models.as_slice().load_self_via_ex(super::#junction_module::Entity, #reverse, db)#await_?;
                let #field = EntityLoader::load_nest_nest(#field, &nest.#field, db)#await_?;

                for (model, #field) in models.iter_mut().zip(#field) {
                    model.#field = #field.into();
                }
            }
        });
        output.load_many_nest.extend(quote! {
            if with.#field {
                let #field = models.as_slice().load_self_via_ex(super::#junction_module::Entity, #reverse, db)#await_?;

                for (model, #field) in models.iter_mut().zip(#field) {
                    if let Some(model) = model.as_mut() {
                        model.#field = #field.into();
                    }
                }
            }
        });
        output.load_many_nest_nest.extend(quote! {
            if with.#field {
                let #field = models.as_slice().load_self_via_ex(super::#junction_module::Entity, #reverse, db)#await_?;

                for (models, #field) in models.iter_mut().zip(#field) {
                    for (model, #field) in models.iter_mut().zip(#field) {
                        model.#field = #field.into();
                    }
                }
            }
        });
    }

    fn expand_load_into(&self, output: &mut EntityLoaderOutput, duplicate_entity: bool) {
        match &self.kind {
            EntityLoaderFieldKind::HasOne if !duplicate_entity => {
                self.expand_load_one_into(output);
            }
            EntityLoaderFieldKind::HasOne => {
                if let Some(relation_enum) = &self.relation_enum {
                    self.expand_load_one_with_rel_into(output, relation_enum);
                }
            }
            EntityLoaderFieldKind::HasMany | EntityLoaderFieldKind::ManyToMany
                if !duplicate_entity =>
            {
                self.expand_load_many_into(output);
            }
            EntityLoaderFieldKind::HasMany => {
                if let Some(relation_enum) = &self.relation_enum {
                    self.expand_load_many_with_rel_into(output, relation_enum);
                }
            }
            EntityLoaderFieldKind::ManyToMany => {}
            EntityLoaderFieldKind::HasOneSelf => {
                if let Some(relation_enum) = &self.relation_enum {
                    self.expand_load_one_self_into(output, relation_enum);
                }
            }
            EntityLoaderFieldKind::HasManySelf => {
                if let Some(relation_enum) = &self.relation_enum {
                    self.expand_load_many_self_into(output, relation_enum);
                }
            }
            EntityLoaderFieldKind::ManyToManySelf {
                junction_module,
                reverse,
            } => {
                if let Some(relation_enum) = &self.relation_enum {
                    self.expand_load_many_self_into(output, relation_enum);
                }
                self.expand_load_many_to_many_self_into(output, junction_module, *reverse);
            }
        }
    }
}

impl EntityLoaderOutput {
    fn from_schema(schema: &EntityLoaderSchema) -> Self {
        let total_count = schema.fields.iter().fold(
            HashMap::<&TypePath, usize>::new(),
            |mut total_count, field| {
                *total_count.entry(&field.entity).or_insert(0) += 1;
                total_count
            },
        );
        let mut relation_tuple_impls = HashSet::<&TypePath>::new();
        let mut select_arity = 1;
        let mut output = Self::default();
        output.select_tuple_fields.push(quote!(model));

        for field in &schema.fields {
            let duplicate_entity = total_count
                .get(&field.entity)
                .is_some_and(|count| *count != 1);

            field.expand_loader_with_field_into(&mut output);
            field.expand_loader_nest_field_into(&mut output);
            field.expand_loader_with_set_impl_into(&mut output, duplicate_entity);
            field.expand_loader_with_2_impl_into(&mut output, duplicate_entity);
            if let EntityLoaderFieldKind::ManyToManySelf {
                junction_module,
                reverse,
            } = &field.kind
            {
                field.expand_many_to_many_self_with_param_into(
                    &mut output,
                    junction_module,
                    *reverse,
                );
            }
            if matches!(
                &field.kind,
                EntityLoaderFieldKind::HasOne
                    | EntityLoaderFieldKind::HasMany
                    | EntityLoaderFieldKind::ManyToMany
            ) && duplicate_entity
                && field.relation_enum.is_some()
                && relation_tuple_impls.insert(&field.entity)
            {
                field.expand_relation_tuple_with_param_into(&mut output);
            }
            if matches!(&field.kind, EntityLoaderFieldKind::HasOne) && !duplicate_entity {
                select_arity += 1;
                if select_arity <= 3 {
                    field.expand_select_one_into(&mut output);
                }
            }
            field.expand_load_into(&mut output, duplicate_entity);
        }

        output
    }
}

pub fn expand_entity_loader(vis: &Visibility, schema: EntityLoaderSchema) -> TokenStream {
    let EntityLoaderOutput {
        loader_with_fields,
        loader_nest_fields,
        select_tuple_fields,
        loader_with_set_impl,
        loader_with_2_impl,
        fetch_select_impl,
        assemble_one,
        load_one,
        load_many,
        load_one_nest,
        load_many_nest,
        load_one_nest_nest,
        load_many_nest_nest,
        with_param_impls,
    } = EntityLoaderOutput::from_schema(&schema);

    let (async_, await_) = if cfg!(feature = "async") {
        (quote!(async), quote!(.await))
    } else {
        (quote!(), quote!())
    };

    let async_trait = if cfg!(feature = "async") {
        quote!(#[async_trait::async_trait])
    } else {
        quote!()
    };

    quote! {

    #[doc = " Generated by sea-orm-macros"]
    #[derive(Clone)]
    #vis struct EntityLoader {
        select: sea_orm::Select<Entity>,
        with: EntityLoaderWith,
        nest: EntityLoaderNest,
    }

    #[doc = " Generated by sea-orm-macros"]
    #[derive(Debug, Default, Clone, PartialEq, Eq)]
    #vis struct EntityReverse;

    impl sea_orm::compound::EntityReverse for EntityReverse {
        type Entity = Entity;
    }

    #[doc = " Generated by sea-orm-macros"]
    #[derive(Debug, Default, Clone, PartialEq, Eq)]
    #vis struct EntityLoaderWith {
        #loader_with_fields
    }

    #[doc = " Generated by sea-orm-macros"]
    #[derive(Debug, Default, Clone, PartialEq, Eq)]
    #vis struct EntityLoaderNest {
        #loader_nest_fields
    }

    impl Entity {
        #[doc = " Generated by sea-orm-macros"]
        pub const REVERSE: EntityReverse = EntityReverse;
    }

    impl EntityLoaderWith {
        #[doc = " Generated by sea-orm-macros"]
        pub fn is_empty(&self) -> bool {
            self == &Self::default()
        }
        #[doc = " Generated by sea-orm-macros"]
        pub fn set(&mut self, target: sea_orm::compound::LoadTarget) {
            #loader_with_set_impl
        }
    }

    #[doc = " Parameters for EntityLoader"]
    #vis trait EntityLoaderWithParam {
        #[doc = " Generated by sea-orm-macros"]
        fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option<sea_orm::compound::LoadTarget>);
    }

    #[automatically_derived]
    impl<R> EntityLoaderWithParam for R
    where
        R: EntityTrait,
        Entity: Related<R>,
    {
        fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option<sea_orm::compound::LoadTarget>) {
            (sea_orm::compound::LoadTarget::TableRef(self.table_ref()), None)
        }
    }

    #[automatically_derived]
    impl<R, S> EntityLoaderWithParam for (R, S)
    where
        R: EntityTrait,
        Entity: Related<R>,
        S: EntityTrait,
        R: Related<S>,
    {
        fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option<sea_orm::compound::LoadTarget>) {
            (
                sea_orm::compound::LoadTarget::TableRef(self.0.table_ref()),
                Some(sea_orm::compound::LoadTarget::TableRef(self.1.table_ref())),
            )
        }
    }

    #[automatically_derived]
    impl<R, S> EntityLoaderWithParam for sea_orm::compound::EntityLoaderWithSelf<R, S>
    where
        R: EntityTrait,
        Entity: Related<R>,
        S: EntityTrait,
        R: RelatedSelfVia<S>,
    {
        fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option<sea_orm::compound::LoadTarget>) {
            (
                sea_orm::compound::LoadTarget::TableRef(self.0.table_ref()),
                Some(sea_orm::compound::LoadTarget::TableRef(self.1.table_ref())),
            )
        }
    }

    #[automatically_derived]
    impl<R, S, SR> EntityLoaderWithParam for sea_orm::compound::EntityLoaderWithSelfRev<R, SR>
    where
        R: EntityTrait,
        Entity: Related<R>,
        S: EntityTrait,
        R: RelatedSelfVia<S>,
        SR: sea_orm::compound::EntityReverse<Entity = S>,
    {
        fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option<sea_orm::compound::LoadTarget>) {
            (
                sea_orm::compound::LoadTarget::TableRef(self.0.table_ref()),
                Some(sea_orm::compound::LoadTarget::TableRefRev(S::default().table_ref())),
            )
        }
    }

    #[automatically_derived]
    impl EntityLoaderWithParam for Relation {
        fn into_with_param(self) -> (sea_orm::compound::LoadTarget, Option<sea_orm::compound::LoadTarget>) {
            (sea_orm::compound::LoadTarget::Relation(self.name()), None)
        }
    }

    #with_param_impls

    #[automatically_derived]
    impl std::fmt::Debug for EntityLoader {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            f.debug_struct("EntityLoader")
            .field("select", &match (Entity::default().schema_name(), Entity::default().table_name()) {
                (Some(s), t) => format!("{s}.{t}"),
                (None, t) => t.to_owned(),
            })
            .field("with", &self.with)
            .field("nest", &self.nest)
            .finish()
        }
    }

    #[automatically_derived]
    impl sea_orm::QueryFilter for EntityLoader {
        type QueryStatement = <sea_orm::Select<Entity> as sea_orm::QueryFilter>::QueryStatement;

        fn query(&mut self) -> &mut Self::QueryStatement {
            sea_orm::QueryFilter::query(&mut self.select)
        }
    }

    #[automatically_derived]
    impl sea_orm::QueryOrder for EntityLoader {
        type QueryStatement = <sea_orm::Select<Entity> as sea_orm::QueryOrder>::QueryStatement;

        fn query(&mut self) -> &mut Self::QueryStatement {
            sea_orm::QueryOrder::query(&mut self.select)
        }
    }

    #[automatically_derived]
    #async_trait
    impl sea_orm::compound::EntityLoaderTrait<Entity> for EntityLoader {
        type ModelEx = ModelEx;

        #async_ fn fetch<C: sea_orm::ConnectionTrait>(self, db: &C, page: u64, page_size: u64) -> Result<Vec<Self::ModelEx>, sea_orm::DbErr> {
            self.fetch(db, page, page_size)#await_
        }

        #async_ fn num_items<C: sea_orm::ConnectionTrait>(self, db: &C, page_size: u64) -> Result<u64, sea_orm::DbErr> {
            self.select.paginate(db, page_size).num_items()#await_
        }
    }

    impl Entity {
        #[doc = " Generated by sea-orm-macros"]
        pub fn load() -> EntityLoader {
            EntityLoader {
                select: Entity::find(),
                with: Default::default(),
                nest: Default::default(),
            }
        }
    }

    impl EntityLoader {
        #[doc = " Generated by sea-orm-macros"]
        pub #async_ fn one<C: sea_orm::ConnectionTrait>(mut self, db: &C) -> Result<Option<ModelEx>, sea_orm::DbErr> {
            use sea_orm::QuerySelect;

            self.select = self.select.limit(1);
            Ok(self.all(db)#await_?.into_iter().next())
        }

        #[doc = " Generated by sea-orm-macros"]
        pub #async_ fn all<C: sea_orm::ConnectionTrait>(self, db: &C) -> Result<Vec<ModelEx>, sea_orm::DbErr> {
            self.fetch(db, 0, 0)#await_
        }

        #[doc = " Generated by sea-orm-macros"]
        pub fn with<T: EntityLoaderWithParam>(mut self, param: T) -> Self {
            match param.into_with_param() {
                (left, None) => self.with_1(left),
                (left, Some(right)) => self.with_2(left, right),
            }
        }

        fn with_1(mut self, load_target: sea_orm::compound::LoadTarget) -> Self {
            self.with.set(load_target);
            self
        }

        fn with_2(mut self, left: sea_orm::compound::LoadTarget, right: sea_orm::compound::LoadTarget) -> Self {
            #loader_with_2_impl
            self
        }

        #[doc = " Generated by sea-orm-macros"]
        #async_ fn fetch<C: sea_orm::ConnectionTrait>(mut self, db: &C, page: u64, page_size: u64) -> Result<Vec<ModelEx>, sea_orm::DbErr> {
            let select = self.select;
            let mut loaded = EntityLoaderWith::default();

            #fetch_select_impl

            let models = if page_size != 0 {
                select.paginate(db, page_size).fetch_page(page)#await_?
            } else {
                select.all(db)#await_?
            };

            let models = models.into_iter().map(|(#select_tuple_fields)| {
                let mut model = model.into_ex();
                #assemble_one
                model
            }).collect::<Vec<_>>();

            let models = Self::load(models, &self.with, &self.nest, db)#await_?;

            Ok(models)
        }

        #[doc = " Generated by sea-orm-macros"]
        pub #async_ fn load<C: sea_orm::ConnectionTrait>(mut models: Vec<ModelEx>, with: &EntityLoaderWith, nest: &EntityLoaderNest, db: &C) -> Result<Vec<ModelEx>, DbErr> {
            use sea_orm::LoaderTraitEx;
            #load_one
            #load_many
            Ok(models)
        }

        #[doc = " Generated by sea-orm-macros"]
        pub #async_ fn load_nest<C: sea_orm::ConnectionTrait>(mut models: Vec<Option<ModelEx>>, with: &EntityLoaderWith, db: &C) -> Result<Vec<Option<ModelEx>>, DbErr> {
            use sea_orm::LoaderTraitEx;
            #load_one_nest
            #load_many_nest
            Ok(models)
        }

        #[doc = " Generated by sea-orm-macros"]
        pub #async_ fn load_nest_nest<C: sea_orm::ConnectionTrait>(mut models: Vec<Vec<ModelEx>>, with: &EntityLoaderWith, db: &C) -> Result<Vec<Vec<ModelEx>>, DbErr> {
            use sea_orm::NestedLoaderTrait;
            #load_one_nest_nest
            #load_many_nest_nest
            Ok(models)
        }
    }

    }
}
