//! {{ model_name }} model and associated builders.

use crate::Row;
use futures::stream::StreamExt;
{%- if has_enums %}
{%- for enum_name in enum_imports %}
use super::enums::{{ enum_name }};
{%- endfor %}
{%- endif %}
{%- if has_composite_types %}
{%- for type_name in composite_type_imports %}
use super::types::{{ type_name }};
{%- endfor %}
{%- endif %}
{%- if has_relations %}
{%- for relation_model in relation_imports %}
use super::{{ relation_model }};
{%- endfor %}
{%- endif %}

fn all_scalar_value_hints() -> &'static [Option<crate::ValueHint>] {
    &[
{%- for field in scalar_fields %}
        {{ field.read_hint_expr }},
{%- endfor %}
    ]
}

fn all_scalar_decode_slots() -> &'static [usize] {
    &[
{%- for field in scalar_fields %}
        {{ loop.index0 }},
{%- endfor %}
    ]
}

fn projected_scalar_value_hints(
    fields: &std::collections::HashSet<String>,
) -> Vec<Option<crate::ValueHint>> {
    let mut hints = Vec::with_capacity({{ scalar_fields | length }});
{%- for field in scalar_fields %}
{%- if field.is_pk %}
    hints.push({{ field.read_hint_expr }});
{%- else %}
    if fields.is_empty() || fields.contains("{{ field.name }}") {
        hints.push({{ field.read_hint_expr }});
    }
{%- endif %}
{%- endfor %}
    hints
}

fn projected_scalar_decode_slots(fields: &std::collections::HashSet<String>) -> Vec<usize> {
    let mut slots = Vec::with_capacity({{ scalar_fields | length }});
{%- for field in scalar_fields %}
{%- if field.is_pk %}
    slots.push({{ loop.index0 }});
{%- else %}
    if fields.is_empty() || fields.contains("{{ field.name }}") {
        slots.push({{ loop.index0 }});
    }
{%- endif %}
{%- endfor %}
    slots
}

fn decode_relation_json_ref<T>(value: &nautilus_core::Value) -> Option<T>
where
    T: serde::de::DeserializeOwned,
{
    match value {
        nautilus_core::Value::Json(json) => serde::Deserialize::deserialize(json).ok(),
        _ => None,
    }
}

fn decode_relation_json_owned<T>(value: nautilus_core::Value) -> Option<T>
where
    T: serde::de::DeserializeOwned,
{
    match value {
        nautilus_core::Value::Json(json) => serde_json::from_value(json).ok(),
        _ => None,
    }
}

fn decode_model_row_with_hints(
    row: crate::Row,
    decode_slots: &[usize],
    hints: &[Option<crate::ValueHint>],
) -> nautilus_core::Result<{{ model_name }}> {
    if decode_slots.len() != hints.len() {
        return Err(nautilus_core::Error::Other(format!(
            "Failed to decode {{ model_name }} row: projection slot count {} does not match hint count {}",
            decode_slots.len(),
            hints.len(),
        )));
    }

    if row.len() < decode_slots.len() {
        return Err(nautilus_core::Error::TypeError(format!(
            "expected at least {} projected scalar columns for {{ model_name }}, got {}",
            decode_slots.len(),
            row.len(),
        )));
    }

{%- for field in scalar_fields %}
    let mut {{ field.name }}: Option<{{ field.rust_type }}> = None;
{%- endfor %}
{%- for rel in relations %}
{%- if rel.is_array %}
    let mut {{ rel.field_name }} = vec![];
{%- else %}
    let mut {{ rel.field_name }} = None;
{%- endif %}
{%- endfor %}

    for (column_idx, (column_name, value)) in row.into_columns_iter().enumerate() {
        if column_idx >= decode_slots.len() {
{%- if relations | length > 0 %}
            match column_name.as_str() {
{%- for rel in relations %}
                "{{ rel.field_name }}_json" => {
{%- if rel.is_array %}
                    {{ rel.field_name }} =
                        decode_relation_json_owned::<Vec<{{ rel.target_model }}>>(value)
                            .unwrap_or_default();
{%- else %}
                    {{ rel.field_name }} =
                        decode_relation_json_owned::<{{ rel.target_model }}>(value).map(Box::new);
{%- endif %}
                }
{%- endfor %}
                _ => {}
            }
{%- endif %}
            continue;
        }

        let value = match hints[column_idx] {
            Some(hint) => crate::normalize_value_with_hint(&column_name, column_idx, value, hint)
                .map_err(|e| {
                    nautilus_core::Error::Other(format!(
                        "Failed to normalize {{ model_name }} row: {}",
                        e
                    ))
                })?,
            None => value,
        };

        match decode_slots[column_idx] {
{%- for field in scalar_fields %}
            {{ loop.index0 }} => {
{%- if field.is_pk %}
                {{ field.name }} = Some(nautilus_core::FromValue::from_value_owned(value)?);
{%- else %}
                {{ field.name }} = match value {
                    nautilus_core::Value::Null => None,
                    other => nautilus_core::FromValue::from_value_owned(other).ok(),
                };
{%- endif %}
            }
{%- endfor %}
            other => {
                return Err(nautilus_core::Error::TypeError(format!(
                    "unknown scalar projection slot {} while decoding {{ model_name }}",
                    other
                )));
            }
        }
    }

    Ok({{ model_name }} {
{%- for field in scalar_fields %}
{%- if field.is_pk %}
        {{ field.name }}: {{ field.name }}.ok_or_else(|| nautilus_core::Error::TypeError(
            "missing required PK column '{{ field.db_name }}' ({{ field.name }})".to_string()
        ))?,
{%- else %}
        {{ field.name }},
{%- endif %}
{%- endfor %}
{%- for field in relation_fields %}
        {{ field.name }},
{%- endfor %}
    })
}

{% include "model_struct.tera" %}

{% include "column_impl.tera" %}

{% include "from_row_impl.tera" %}

{% include "columns_struct.tera" %}

{% include "delegate.tera" %}

{% include "find_many.tera" %}

{% include "create.tera" %}

{% include "create_many.tera" %}

{% include "update.tera" %}

{% include "delete.tera" %}
