/// {{trait.description | replace("\n", " ")}}
pub(crate) trait {{trait.name | title}}ClientSubscriber: Sync + Send {
{% for var in trait.vars %}
    fn on_{{ var.name | snake }}(&self, context: &Context, value: {{ var.data_type.name }});
{% endfor %}

{%- if trait.errors %}
    fn on_error_raised(&self, context: &Context, error: ::everestrs::ErrorType<errors::{{ trait.name | snake }}::Error>);

    fn on_error_cleared(&self, context: &Context, error: ::everestrs::ErrorType<errors::{{ trait.name | snake }}::Error>);
{%- endif %}

}

#[cfg(all(feature = "mockall", feature = "trait"))]
mockall::mock! {
    pub(crate) {{trait.name | title}}ClientSubscriber {}
    impl {{trait.name | title}}ClientSubscriber for {{trait.name | title}}ClientSubscriber {
{% for var in trait.vars %}
        fn on_{{ var.name | snake }}<'a>(&self, context: &Context<'a>, value: {{ var.data_type.name }});
{% endfor %}

{%- if trait.errors %}
        fn on_error_raised<'a>(&self, context: &Context<'a>, error: ::everestrs::ErrorType<errors::{{ trait.name | snake }}::Error>);

        fn on_error_cleared<'a>(&self, context: &Context<'a>, error: ::everestrs::ErrorType<errors::{{ trait.name | snake }}::Error>);
{%- endif %}
    }
}

fn dispatch_variable_to_{{ trait.name | snake }}(
   context: &Context,
   client_subscriber: &dyn {{trait.name | title}}ClientSubscriber,
   name: &str,
   value: __serde_json::Value,
) -> ::everestrs::Result<()> {
    match name {
{%- for var in trait.vars %}
        "{{ var.name }}" => {
            let v: {{ var.data_type.name }} = __serde_json::from_value(value)
                .map_err(|e| ::everestrs::Error::MessageParsingError(format!("Failed to deserialize variable `{{ var.name }}`: {e:?})")))?;
            client_subscriber.on_{{ var.name | snake }}(context, v);
            Ok(())
        },
{%- endfor %}
        other => Err(::everestrs::Error::MessageParsingError(format!("Unknown variable {other} received.").to_string())),
   }
}

fn dispatch_error_to_{{ trait.name | snake }} (
    context: &Context,
    client_subscriber: &dyn {{ trait.name | title }}ClientSubscriber,
    error: ::everestrs::FfiErrorType,
    raised: bool
) {
{%- if trait.errors %}
    // The type is errors::{{ trait.name | snake }}::Error
    let Ok(v) = __serde_yaml::from_str(&error.error_type) else {
        everestrs::log::error!("Failed to deserialize error `{}`", error.error_type);
        return;
    };

    let error_type = ::everestrs::ErrorType {
        error_type: v,
        description: error.description,
        message: error.message,
        severity: error.severity,
    };

    if raised {
        client_subscriber.on_error_raised(context, error_type);
    } else {
        client_subscriber.on_error_cleared(context, error_type);
    }
{%- endif %}
}

pub(crate) mod __mockall_{{trait.name | snake }}_client {

use super::__serde_json;
use super::types;
use super::errors;

#[derive(Clone)]
pub(crate) struct {{trait.name | title }}ClientPublisher {
   pub(super) implementation_id: &'static str,
   pub(super) runtime: ::std::sync::Weak<::everestrs::Runtime>,
   pub(super) index: usize,
}

impl {{trait.name | title }}ClientPublisher {
{%- for cmd in trait.cmds %}
   /// {{cmd.description | replace("\n", " ")}}
   ///
   {%- for arg in cmd.arguments %}
   /// `{{arg.name}}`: {{arg.description | replace("\n", " ")}}
   {%- endfor %}
   {% if cmd.result -%}
   ///
   /// Returns: {{cmd.result.description | replace("\n", " ")}}
   {% endif -%}
   pub(crate) fn {{cmd.name | identifier}}(&self,
   {%- for arg in cmd.arguments %}
      {{arg.name | identifier }}: {{arg.data_type.name}},
   {%- endfor %}
   ) -> ::everestrs::Result<{%- if cmd.result -%}
      {{cmd.result.data_type.name}}
   {%- else -%}
      ()
   {%- endif -%}
      > {
        let args = __serde_json::json!({
{%- for arg in cmd.arguments %}
            "{{arg.name}}": {{arg.name | identifier}},
{%- endfor %}
        });
        let rt = self.runtime.upgrade().ok_or_else(|| {
            ::everestrs::Error::HandlerException(
                "publisher used after Module was dropped".into(),
            )
        })?;
        rt.call_command(self.implementation_id, self.index, "{{ cmd.name }}", &args)
      }
{% endfor %}

}

#[cfg(all(feature = "mockall", not(feature = "trait")))]
mockall::mock!{
   pub(crate) {{trait.name | title }}ClientPublisher {
{%- for cmd in trait.cmds %}
   pub(crate) fn {{cmd.name | identifier}}(&self,
   {%- for arg in cmd.arguments %}
      {{arg.name | identifier }}: {{arg.data_type.name}},
   {%- endfor %}
   ) -> ::everestrs::Result<{%- if cmd.result -%}
      {{cmd.result.data_type.name}}
   {%- else -%}
      ()
   {%- endif -%}
      >;
{% endfor %}
   }

   impl Clone for {{trait.name | title }}ClientPublisher {
      fn clone(&self) -> Self;
   }
}

}
#[cfg_attr(all(feature = "mockall", not(feature = "trait")), mockall_double::double)]
pub(crate) use __mockall_{{trait.name | snake }}_client::{{trait.name | title }}ClientPublisher;
