#[allow(unused_imports)]
use domain::models::FromRawValue as _;
#[allow(unused_imports)]
use domain::models::@{ db|snake|to_var_name }@::@{ rel_mod }@::{
    self as _domain_, @{ pascal_name }@Updater as _, @{ pascal_name }@UpdaterBase as _
};
#[allow(unused_imports)]
use domain::value_objects;
#[allow(unused_imports)]
use senax_common::types::blob::{ApiToBlob as _, BlobToApi as _};
#[allow(unused_imports)]
use std::collections::HashMap;

@%- if !no_read %@

@{ def.label|label0 -}@
#[derive(async_graphql::SimpleObject, serde::Serialize)]
#[graphql(name = "@{ graphql_name }@")]
#[derive(utoipa::ToSchema)]
#[schema(as = @{ graphql_name }@)]
pub struct ResObj@{ rel_name|pascal }@ {
    #[graphql(name = "_id")]
    #[schema(value_type = String)]
    pub _id: async_graphql::ID,
@%- if camel_case %@
@{- def.for_api_response()|fmt_join("
{label_wo_hash}{res_api_schema_type}    pub {var}: {res_api_type},", "") }@
@{- def.relations_one_for_api_response()|fmt_rel_join("
{label_wo_hash}    pub {rel_name}: Option<_{raw_rel_name}::ResObj{rel_name_pascal}>,", "") }@
@{- def.relations_many_for_api_response()|fmt_rel_join("
{label_wo_hash}    pub {rel_name}: Vec<_{raw_rel_name}::ResObj{rel_name_pascal}>,", "") }@
@{- def.relations_belonging_for_api_response()|fmt_rel_join("
    #[graphql(name = \"_{raw_rel_name}_id\")]
    #[schema(value_type = Option<String>)]
    pub _{raw_rel_name}_id: Option<async_graphql::ID>,
{label_wo_hash}    pub {rel_name}: Option<_{raw_rel_name}::ResObj{rel_name_pascal}>,", "") }@
@%- else %@
@{- def.for_api_response()|fmt_join("
{label_wo_hash}{res_api_schema_type}    #[graphql(name = \"{raw_var}\")]
    pub {var}: {res_api_type},", "") }@
@{- def.relations_one_for_api_response()|fmt_rel_join("
{label_wo_hash}    #[graphql(name = \"{raw_rel_name}\")]
    pub {rel_name}: Option<_{raw_rel_name}::ResObj{rel_name_pascal}>,", "") }@
@{- def.relations_many_for_api_response()|fmt_rel_join("
{label_wo_hash}    #[graphql(name = \"{raw_rel_name}\")]
    pub {rel_name}: Vec<_{raw_rel_name}::ResObj{rel_name_pascal}>,", "") }@
@{- def.relations_belonging_for_api_response()|fmt_rel_join("
    #[graphql(name = \"_{raw_rel_name}_id\")]
    #[schema(value_type = Option<String>)]
    pub _{raw_rel_name}_id: Option<async_graphql::ID>,
{label_wo_hash}    #[graphql(name = \"{raw_rel_name}\")]
    pub {rel_name}: Option<_{raw_rel_name}::ResObj{rel_name_pascal}>,", "") }@
@%- endif %@
}

impl From<&dyn _domain_::@{ pascal_name }@> for ResObj@{ rel_name|pascal }@ {
    fn from(v: &dyn _domain_::@{ pascal_name }@) -> Self {
        Self {
            _id: v.into(),
            @{- def.for_api_response()|fmt_join("
            {var}: v.{var}(){to_res_api_type},", "") }@
            @{- def.relations_one_for_api_response()|fmt_rel_join("
            {rel_name}: v.{rel_name}().unwrap_or_default().map(|v| v.into()),", "") }@
            @{- def.relations_many_for_api_response()|fmt_rel_join("
            {rel_name}: v.{rel_name}().map(|l| l.map(|v| v.into()).collect()).unwrap_or_default(),", "") }@
            @{- def.relations_belonging_for_api_response()|fmt_rel_join("
            _{raw_rel_name}_id: v._{raw_rel_name}_id().map(|v| v.into()),
            {rel_name}: v.{rel_name}().unwrap_or_default().map(|v| v.into()),", "") }@
        }
    }
}

impl From<&dyn _domain_::@{ pascal_name }@Cache> for ResObj@{ rel_name|pascal }@ {
    fn from(v: &dyn _domain_::@{ pascal_name }@Cache) -> Self {
        Self {
            _id: v.into(),
            @{- def.for_api_response()|fmt_join("
            {var}: v.{var}(){to_res_api_type},", "") }@
            @{- def.relations_one_for_api_response()|fmt_rel_join("
            {rel_name}: v.{rel_name}().unwrap_or_default().map(|v| (&*v).into()),", "") }@
            @{- def.relations_many_for_api_response()|fmt_rel_join("
            {rel_name}: v.{rel_name}().map(|l| l.iter().map(|v| (&**v).into()).collect()).unwrap_or_default(),", "") }@
            @{- def.relations_belonging_for_api_response()|fmt_rel_join("
            _{raw_rel_name}_id: v._{raw_rel_name}_id().map(|v| v.into()),
            {rel_name}: v.{rel_name}().unwrap_or_default().map(|v| (&*v).into()),", "") }@
        }
    }
}

#[allow(unused_mut)]
#[allow(clippy::needless_update)]
pub fn joiner(_look_ahead: async_graphql::Lookahead<'_>) -> Option<Box<_domain_::Joiner_>> {
    if !_look_ahead.exists() {
        return None;
    }
    let joiner = _domain_::Joiner_ {
        @%- if camel_case %@
        @{- def.relations_one_for_api_response()|fmt_rel_join("
        {rel_name}: _{raw_rel_name}::joiner(_look_ahead.field(\"{rel_name_camel}\")),", "") }@
        @{- def.relations_many_for_api_response()|fmt_rel_join("
        {rel_name}: _{raw_rel_name}::joiner(_look_ahead.field(\"{rel_name_camel}\")),", "") }@
        @{- def.relations_belonging_for_api_response()|fmt_rel_join("
        {rel_name}: _{raw_rel_name}::joiner(_look_ahead.field(\"{rel_name_camel}\")),", "") }@
        @%- else %@
        @{- def.relations_one_for_api_response()|fmt_rel_join("
        {rel_name}: _{raw_rel_name}::joiner(_look_ahead.field(\"{raw_rel_name}\")),", "") }@
        @{- def.relations_many_for_api_response()|fmt_rel_join("
        {rel_name}: _{raw_rel_name}::joiner(_look_ahead.field(\"{raw_rel_name}\")),", "") }@
        @{- def.relations_belonging_for_api_response()|fmt_rel_join("
        {rel_name}: _{raw_rel_name}::joiner(_look_ahead.field(\"{raw_rel_name}\")),", "") }@
        @%- endif %@
        ..Default::default()
    };
    Some(Box::new(joiner))
}

#[allow(unused_mut)]
#[allow(dead_code)]
#[allow(clippy::needless_update)]
pub fn reader_joiner() -> Option<Box<_domain_::Joiner_>> {
    let joiner = _domain_::Joiner_ {
        @{- def.relations_one_for_api_response()|fmt_rel_join("
        {rel_name}: _{raw_rel_name}::reader_joiner(),", "") }@
        @{- def.relations_many_for_api_response()|fmt_rel_join("
        {rel_name}: _{raw_rel_name}::reader_joiner(),", "") }@
        @{- def.relations_belonging_for_api_response()|fmt_rel_join("
        {rel_name}: _{raw_rel_name}::reader_joiner(),", "") }@
        ..Default::default()
    };
    Some(Box::new(joiner))
}
@%- endif %@
@%- if !api_def.disable_mutation && !no_update %@

#[allow(unused_mut)]
#[allow(clippy::needless_update)]
pub fn updater_joiner() -> Option<Box<_domain_::Joiner_>> {
    let joiner = _domain_::Joiner_ {
        @{- def.relations_one_for_api_request()|fmt_rel_join("
        {rel_name}: _{raw_rel_name}::updater_joiner(),", "") }@
        @{- def.relations_many_for_api_request()|fmt_rel_join("
        {rel_name}: _{raw_rel_name}::updater_joiner(),", "") }@
        ..Default::default()
    };
    Some(Box::new(joiner))
}

use serde::{Deserialize, Serialize};
use crate::auth::AuthInfo;
use crate::db::RepositoriesImpl;

@{ def.label|label0 -}@
#[derive(Debug, async_graphql::InputObject, validator::Validate, Serialize, Deserialize, schemars::JsonSchema)]
#[graphql(name = "Req@{ graphql_name }@")]
#[derive(utoipa::ToSchema)]
#[schema(as = Req@{ graphql_name }@)]
pub struct ReqObj@{ rel_name|pascal }@ {
@%- if camel_case %@
@{- def.auto_primary()|fmt_join("
{label_wo_hash}{graphql_secret}{api_validate}{api_serde_default}    pub {var}: {req_api_option_type},", "") }@
@{- def.for_api_request_except(rel_id)|fmt_join("
{label_wo_hash}{graphql_secret}{api_validate}{api_serde_default}    pub {var}: {req_api_type},", "") }@
@{- def.relations_one_for_api_request()|fmt_rel_join("
{label_wo_hash}    pub {rel_name}: Option<_{raw_rel_name}::ReqObj{rel_name_pascal}>,", "") }@
@{- def.relations_many_for_api_request()|fmt_rel_join("
{label_wo_hash}    pub {rel_name}: Option<Vec<_{raw_rel_name}::ReqObj{rel_name_pascal}>>,", "") }@
@%- else %@
@{- def.auto_primary()|fmt_join("
{label_wo_hash}    #[graphql(name = \"{raw_var}\")]
{graphql_secret}{api_validate}{api_serde_default}    pub {var}: {req_api_option_type},", "") }@
@{- def.for_api_request_except(rel_id)|fmt_join("
{label_wo_hash}    #[graphql(name = \"{raw_var}\")]
{graphql_secret}{api_validate}{api_serde_default}    pub {var}: {req_api_type},", "") }@
@{- def.relations_one_for_api_request()|fmt_rel_join("
{label_wo_hash}    #[graphql(name = \"{raw_rel_name}\")]
    pub {rel_name}: Option<_{raw_rel_name}::ReqObj{rel_name_pascal}>,", "") }@
@{- def.relations_many_for_api_request()|fmt_rel_join("
{label_wo_hash}    #[graphql(name = \"{raw_rel_name}\")]
    pub {rel_name}: Option<Vec<_{raw_rel_name}::ReqObj{rel_name_pascal}>>,", "") }@
@%- endif %@
}

@{- def.fields_with_default()|fmt_join("
fn default_{raw_var}() -> {req_api_type} {
    {api_default}
}", "") }@

#[allow(clippy::useless_conversion)]
#[allow(clippy::redundant_closure_call)]
impl From<&mut dyn _domain_::@{ pascal_name }@Updater> for ReqObj@{ rel_name|pascal }@ {
    fn from(v: &mut dyn _domain_::@{ pascal_name }@Updater) -> Self {
        Self {
            @{- def.auto_primary()|fmt_join("
            {var}: Some(v.{var}(){to_req_api_type}),", "") }@
            @{- def.for_api_request_except(rel_id)|fmt_join("
            {var}: v.{var}(){to_req_api_type},", "") }@
            @{- def.relations_one_for_api_request()|fmt_rel_join("
            {rel_name}: (|| v.{rel_name}().unwrap().map(|v| v.into()))(),", "") }@
            @{- def.relations_many_for_api_request()|fmt_rel_join("
            {rel_name}: (|| Some(v.{rel_name}().unwrap().iter_mut().map(|v| v.into()).collect()))(),", "") }@
        }
    }
}

#[allow(clippy::let_and_return)]
#[allow(unused_mut)]
#[allow(unused_variables)]
pub fn create_entity(input: ReqObj@{ rel_name|pascal }@, repo: &RepositoriesImpl, auth: &AuthInfo) -> Box<dyn _domain_::@{ pascal_name }@Updater> {
    let mut obj = _domain_::@{ pascal_name }@Factory {
@{- def.non_auto_primary_for_factory()|fmt_join_with_foreign_default("
        {var}: {from_api_rel_type},", "", rel_id) }@
    }
    .create(repo);
    @{- def.relations_one_for_api_request()|fmt_rel_join("
    if let Some(input) = input.{rel_name} {
        obj.set_{raw_rel_name}(_{raw_rel_name}::create_entity(input, repo, auth));
    }", "") }@
    @{- def.relations_many_for_api_request()|fmt_rel_join("
    if let Some(data_list) = input.{rel_name} {
        obj.replace_{raw_rel_name}(_{raw_rel_name}::create_list(data_list, repo, auth));
    }", "") }@
    obj
}
@%- if has_many %@

pub fn create_list(
    data_list: Vec<ReqObj@{ rel_name|pascal }@>,
    repo: &RepositoriesImpl,
    auth: &AuthInfo,
) -> Vec<Box<dyn _domain_::@{ pascal_name }@Updater>> {
    data_list.into_iter().map(|v| create_entity(v, repo, auth)).collect()
}

pub fn update_list(
    list: Vec<Box<dyn _domain_::@{ pascal_name }@Updater>>,
    data_list: Vec<ReqObj@{ rel_name|pascal }@>,
    repo: &RepositoriesImpl,
    auth: &AuthInfo,
) -> anyhow::Result<Vec<Box<dyn _domain_::@{ pascal_name }@Updater>>> {
    let mut map: HashMap<_, _> = list
        .into_iter()
        .map(|mut v| { v.mark_for_delete(); v} )
        .map(|v| (v.@{ def.primary_except(rel_id)|to_var_name }@()@% if def.primary_except_has_inner(rel_id) %@.inner()@% endif %@, v))
        .collect();
    let mut list = Vec::new();
    for row in data_list.into_iter() {
        @%- if def.is_auto_primary_except(rel_id) %@
        if let Some(id) = row.@{ def.primary_except(rel_id)|to_var_name }@ {
            if let Some(mut updater) = map.remove(&id) {
                updater.unmark_for_delete();
                update_updater(&mut *updater, row, repo, auth)?;
                list.push(updater);
            } else {
                anyhow::bail!("The @{ def.primary_except(rel_id) }@ of @{ rel_name }@ is invalid.");
            }
        } else {
            list.push(create_entity(row, repo, auth));
        }
        @%- else %@
        if let Some(mut updater) = map.remove(&row.@{ def.primary_except(rel_id)|to_var_name }@) {
            updater.unmark_for_delete();
            update_updater(&mut *updater, row, repo, auth)?;
            list.push(updater);
        } else {
            list.push(create_entity(row, repo, auth));
        }
        @%- endif %@
    }
    map.into_iter().for_each(|(_, v)| {
        list.push(v);
    });
    Ok(list)
}
@%- endif %@
@%- if !replace %@

#[allow(unused_variables)]
pub fn update_updater(
    updater: &mut dyn _domain_::@{ pascal_name }@Updater,
    input: ReqObj@{ rel_name|pascal }@,
    repo: &RepositoriesImpl,
    auth: &AuthInfo,
) -> anyhow::Result<()> {
@{- def.for_api_request_except_without_primary(rel_id)|fmt_join("
    updater.set_{raw_var}({from_api_type_for_update});", "") }@
@{- def.relations_one_for_api_request_with_replace_type(true)|fmt_rel_join("
    if let Some(input) = input.{rel_name} {
        updater.set_{raw_rel_name}(_{raw_rel_name}::create_entity(input, repo, auth));
    }", "") }@
@{- def.relations_one_for_api_request_with_replace_type(false)|fmt_rel_join("
    if let Some(input) = input.{rel_name} {
        if let Some(updater) = updater.{rel_name}().unwrap_or_default() {
            _{raw_rel_name}::update_updater(updater, input, repo, auth)?;
        } else {
            updater.set_{raw_rel_name}(_{raw_rel_name}::create_entity(input, repo, auth));
        }
    }", "") }@
@{- def.relations_many_for_api_request()|fmt_rel_join("
    if let Some(data_list) = input.{rel_name} {
        let list = updater.take_{raw_rel_name}().unwrap_or_default();
        updater.replace_{raw_rel_name}(_{raw_rel_name}::update_list(list, data_list, repo, auth)?);        
    }", "") }@
    Ok(())
}
@%- endif %@
@%- endif %@