//! Avatar-related database queries.

use std::collections::HashMap;

use sqlx::{PgExecutor, PgPool, postgres::PgConnection};
use uuid::Uuid;

use crate::models::{ActiveAvatar, AvatarWithPaths, EmotionAvailability, EmotionState};
use chattyness_error::AppError;

/// Get the active avatar for a user in a realm.
pub async fn get_active_avatar<'e>(
    executor: impl PgExecutor<'e>,
    user_id: Uuid,
    realm_id: Uuid,
) -> Result<Option<ActiveAvatar>, AppError> {
    let avatar = sqlx::query_as::<_, ActiveAvatar>(
        r#"
        SELECT user_id, realm_id, avatar_id, current_emotion, updated_at
        FROM auth.active_avatars
        WHERE user_id = $1 AND realm_id = $2
        "#,
    )
    .bind(user_id)
    .bind(realm_id)
    .fetch_optional(executor)
    .await?;

    Ok(avatar)
}

/// Set the current emotion for a user in a realm.
/// Returns the full emotion layer (9 asset paths) for the new emotion.
pub async fn set_emotion<'e>(
    executor: impl PgExecutor<'e>,
    user_id: Uuid,
    realm_id: Uuid,
    emotion: EmotionState,
) -> Result<[Option<String>; 9], AppError> {
    // Map emotion to column prefix
    let emotion_prefix = match emotion {
        EmotionState::Neutral => "e_neutral",
        EmotionState::Happy => "e_happy",
        EmotionState::Sad => "e_sad",
        EmotionState::Angry => "e_angry",
        EmotionState::Surprised => "e_surprised",
        EmotionState::Thinking => "e_thinking",
        EmotionState::Laughing => "e_laughing",
        EmotionState::Crying => "e_crying",
        EmotionState::Love => "e_love",
        EmotionState::Confused => "e_confused",
        EmotionState::Sleeping => "e_sleeping",
        EmotionState::Wink => "e_wink",
    };

    // Get the numeric index for the database
    let emotion_index = emotion.to_index() as i16;

    // Build dynamic query for the specific emotion's 9 positions
    let query = format!(
        r#"
        WITH updated AS (
            UPDATE auth.active_avatars
            SET current_emotion = $3, updated_at = now()
            WHERE user_id = $1 AND realm_id = $2
            RETURNING avatar_id
        )
        SELECT
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.{prefix}_0) as p0,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.{prefix}_1) as p1,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.{prefix}_2) as p2,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.{prefix}_3) as p3,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.{prefix}_4) as p4,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.{prefix}_5) as p5,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.{prefix}_6) as p6,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.{prefix}_7) as p7,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.{prefix}_8) as p8
        FROM updated u
        JOIN auth.avatars a ON a.id = u.avatar_id
        "#,
        prefix = emotion_prefix
    );

    let result = sqlx::query_as::<_, EmotionLayerRow>(&query)
        .bind(user_id)
        .bind(realm_id)
        .bind(emotion_index)
        .fetch_optional(executor)
        .await?;

    match result {
        Some(row) => Ok([
            row.p0, row.p1, row.p2, row.p3, row.p4, row.p5, row.p6, row.p7, row.p8,
        ]),
        None => Err(AppError::NotFound(
            "No active avatar for this user in this realm".to_string(),
        )),
    }
}

/// Row type for emotion layer query.
#[derive(Debug, sqlx::FromRow)]
struct EmotionLayerRow {
    p0: Option<String>,
    p1: Option<String>,
    p2: Option<String>,
    p3: Option<String>,
    p4: Option<String>,
    p5: Option<String>,
    p6: Option<String>,
    p7: Option<String>,
    p8: Option<String>,
}

/// Get emotion availability for a user's avatar in a realm.
///
/// Returns which emotions have assets configured (any of positions 0-8 non-null)
/// and the center position (4) preview path for each emotion.
pub async fn get_emotion_availability<'e>(
    executor: impl PgExecutor<'e>,
    user_id: Uuid,
    realm_id: Uuid,
) -> Result<EmotionAvailability, AppError> {
    let row = sqlx::query_as::<_, EmotionAvailabilityRow>(
        r#"
        SELECT
            -- Neutral (0): check if any position has asset
            (a.e_neutral_0 IS NOT NULL OR a.e_neutral_1 IS NOT NULL OR a.e_neutral_2 IS NOT NULL OR
             a.e_neutral_3 IS NOT NULL OR a.e_neutral_4 IS NOT NULL OR a.e_neutral_5 IS NOT NULL OR
             a.e_neutral_6 IS NOT NULL OR a.e_neutral_7 IS NOT NULL OR a.e_neutral_8 IS NOT NULL) as avail_0,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.e_neutral_4) as preview_0,

            -- Happy (1)
            (a.e_happy_0 IS NOT NULL OR a.e_happy_1 IS NOT NULL OR a.e_happy_2 IS NOT NULL OR
             a.e_happy_3 IS NOT NULL OR a.e_happy_4 IS NOT NULL OR a.e_happy_5 IS NOT NULL OR
             a.e_happy_6 IS NOT NULL OR a.e_happy_7 IS NOT NULL OR a.e_happy_8 IS NOT NULL) as avail_1,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.e_happy_4) as preview_1,

            -- Sad (2)
            (a.e_sad_0 IS NOT NULL OR a.e_sad_1 IS NOT NULL OR a.e_sad_2 IS NOT NULL OR
             a.e_sad_3 IS NOT NULL OR a.e_sad_4 IS NOT NULL OR a.e_sad_5 IS NOT NULL OR
             a.e_sad_6 IS NOT NULL OR a.e_sad_7 IS NOT NULL OR a.e_sad_8 IS NOT NULL) as avail_2,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.e_sad_4) as preview_2,

            -- Angry (3)
            (a.e_angry_0 IS NOT NULL OR a.e_angry_1 IS NOT NULL OR a.e_angry_2 IS NOT NULL OR
             a.e_angry_3 IS NOT NULL OR a.e_angry_4 IS NOT NULL OR a.e_angry_5 IS NOT NULL OR
             a.e_angry_6 IS NOT NULL OR a.e_angry_7 IS NOT NULL OR a.e_angry_8 IS NOT NULL) as avail_3,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.e_angry_4) as preview_3,

            -- Surprised (4)
            (a.e_surprised_0 IS NOT NULL OR a.e_surprised_1 IS NOT NULL OR a.e_surprised_2 IS NOT NULL OR
             a.e_surprised_3 IS NOT NULL OR a.e_surprised_4 IS NOT NULL OR a.e_surprised_5 IS NOT NULL OR
             a.e_surprised_6 IS NOT NULL OR a.e_surprised_7 IS NOT NULL OR a.e_surprised_8 IS NOT NULL) as avail_4,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.e_surprised_4) as preview_4,

            -- Thinking (5)
            (a.e_thinking_0 IS NOT NULL OR a.e_thinking_1 IS NOT NULL OR a.e_thinking_2 IS NOT NULL OR
             a.e_thinking_3 IS NOT NULL OR a.e_thinking_4 IS NOT NULL OR a.e_thinking_5 IS NOT NULL OR
             a.e_thinking_6 IS NOT NULL OR a.e_thinking_7 IS NOT NULL OR a.e_thinking_8 IS NOT NULL) as avail_5,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.e_thinking_4) as preview_5,

            -- Laughing (6)
            (a.e_laughing_0 IS NOT NULL OR a.e_laughing_1 IS NOT NULL OR a.e_laughing_2 IS NOT NULL OR
             a.e_laughing_3 IS NOT NULL OR a.e_laughing_4 IS NOT NULL OR a.e_laughing_5 IS NOT NULL OR
             a.e_laughing_6 IS NOT NULL OR a.e_laughing_7 IS NOT NULL OR a.e_laughing_8 IS NOT NULL) as avail_6,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.e_laughing_4) as preview_6,

            -- Crying (7)
            (a.e_crying_0 IS NOT NULL OR a.e_crying_1 IS NOT NULL OR a.e_crying_2 IS NOT NULL OR
             a.e_crying_3 IS NOT NULL OR a.e_crying_4 IS NOT NULL OR a.e_crying_5 IS NOT NULL OR
             a.e_crying_6 IS NOT NULL OR a.e_crying_7 IS NOT NULL OR a.e_crying_8 IS NOT NULL) as avail_7,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.e_crying_4) as preview_7,

            -- Love (8)
            (a.e_love_0 IS NOT NULL OR a.e_love_1 IS NOT NULL OR a.e_love_2 IS NOT NULL OR
             a.e_love_3 IS NOT NULL OR a.e_love_4 IS NOT NULL OR a.e_love_5 IS NOT NULL OR
             a.e_love_6 IS NOT NULL OR a.e_love_7 IS NOT NULL OR a.e_love_8 IS NOT NULL) as avail_8,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.e_love_4) as preview_8,

            -- Confused (9)
            (a.e_confused_0 IS NOT NULL OR a.e_confused_1 IS NOT NULL OR a.e_confused_2 IS NOT NULL OR
             a.e_confused_3 IS NOT NULL OR a.e_confused_4 IS NOT NULL OR a.e_confused_5 IS NOT NULL OR
             a.e_confused_6 IS NOT NULL OR a.e_confused_7 IS NOT NULL OR a.e_confused_8 IS NOT NULL) as avail_9,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.e_confused_4) as preview_9,

            -- Sleeping (10)
            (a.e_sleeping_0 IS NOT NULL OR a.e_sleeping_1 IS NOT NULL OR a.e_sleeping_2 IS NOT NULL OR
             a.e_sleeping_3 IS NOT NULL OR a.e_sleeping_4 IS NOT NULL OR a.e_sleeping_5 IS NOT NULL OR
             a.e_sleeping_6 IS NOT NULL OR a.e_sleeping_7 IS NOT NULL OR a.e_sleeping_8 IS NOT NULL) as avail_10,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.e_sleeping_4) as preview_10,

            -- Wink (11)
            (a.e_wink_0 IS NOT NULL OR a.e_wink_1 IS NOT NULL OR a.e_wink_2 IS NOT NULL OR
             a.e_wink_3 IS NOT NULL OR a.e_wink_4 IS NOT NULL OR a.e_wink_5 IS NOT NULL OR
             a.e_wink_6 IS NOT NULL OR a.e_wink_7 IS NOT NULL OR a.e_wink_8 IS NOT NULL) as avail_11,
            (SELECT prop_asset_path FROM auth.inventory WHERE id = a.e_wink_4) as preview_11

        FROM auth.active_avatars aa
        JOIN auth.avatars a ON aa.avatar_id = a.id
        WHERE aa.user_id = $1 AND aa.realm_id = $2
        "#,
    )
    .bind(user_id)
    .bind(realm_id)
    .fetch_optional(executor)
    .await?;

    match row {
        Some(r) => Ok(r.into()),
        None => Ok(EmotionAvailability::default()),
    }
}

/// Row type for emotion availability query.
#[derive(Debug, sqlx::FromRow)]
struct EmotionAvailabilityRow {
    avail_0: Option<bool>,
    preview_0: Option<String>,
    avail_1: Option<bool>,
    preview_1: Option<String>,
    avail_2: Option<bool>,
    preview_2: Option<String>,
    avail_3: Option<bool>,
    preview_3: Option<String>,
    avail_4: Option<bool>,
    preview_4: Option<String>,
    avail_5: Option<bool>,
    preview_5: Option<String>,
    avail_6: Option<bool>,
    preview_6: Option<String>,
    avail_7: Option<bool>,
    preview_7: Option<String>,
    avail_8: Option<bool>,
    preview_8: Option<String>,
    avail_9: Option<bool>,
    preview_9: Option<String>,
    avail_10: Option<bool>,
    preview_10: Option<String>,
    avail_11: Option<bool>,
    preview_11: Option<String>,
}

impl From<EmotionAvailabilityRow> for EmotionAvailability {
    fn from(row: EmotionAvailabilityRow) -> Self {
        Self {
            available: [
                row.avail_0.unwrap_or(false),
                row.avail_1.unwrap_or(false),
                row.avail_2.unwrap_or(false),
                row.avail_3.unwrap_or(false),
                row.avail_4.unwrap_or(false),
                row.avail_5.unwrap_or(false),
                row.avail_6.unwrap_or(false),
                row.avail_7.unwrap_or(false),
                row.avail_8.unwrap_or(false),
                row.avail_9.unwrap_or(false),
                row.avail_10.unwrap_or(false),
                row.avail_11.unwrap_or(false),
            ],
            preview_paths: [
                row.preview_0,
                row.preview_1,
                row.preview_2,
                row.preview_3,
                row.preview_4,
                row.preview_5,
                row.preview_6,
                row.preview_7,
                row.preview_8,
                row.preview_9,
                row.preview_10,
                row.preview_11,
            ],
        }
    }
}

/// Get the full avatar with all inventory UUIDs resolved to asset paths.
///
/// This function uses two queries:
/// 1. Fetch the avatar row with all 135 UUID slots
/// 2. Bulk resolve all UUIDs to asset paths with a single inventory query
///
/// The result enables client-side emotion availability computation and rendering.
pub async fn get_avatar_with_paths(
    pool: &PgPool,
    user_id: Uuid,
    realm_id: Uuid,
) -> Result<Option<AvatarWithPaths>, AppError> {
    // Query 1: Get the avatar row with current_emotion from active_avatars
    let avatar_row = sqlx::query_as::<_, AvatarWithEmotion>(
        r#"
        SELECT
            a.*,
            aa.current_emotion
        FROM auth.active_avatars aa
        JOIN auth.avatars a ON aa.avatar_id = a.id
        WHERE aa.user_id = $1 AND aa.realm_id = $2
        "#,
    )
    .bind(user_id)
    .bind(realm_id)
    .fetch_optional(pool)
    .await?;

    let Some(avatar) = avatar_row else {
        return Ok(None);
    };

    // Collect all non-null UUIDs from the avatar slots
    let mut uuids: Vec<Uuid> = Vec::new();

    // Content layers
    collect_uuids(
        &mut uuids,
        &[
            avatar.l_skin_0,
            avatar.l_skin_1,
            avatar.l_skin_2,
            avatar.l_skin_3,
            avatar.l_skin_4,
            avatar.l_skin_5,
            avatar.l_skin_6,
            avatar.l_skin_7,
            avatar.l_skin_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.l_clothes_0,
            avatar.l_clothes_1,
            avatar.l_clothes_2,
            avatar.l_clothes_3,
            avatar.l_clothes_4,
            avatar.l_clothes_5,
            avatar.l_clothes_6,
            avatar.l_clothes_7,
            avatar.l_clothes_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.l_accessories_0,
            avatar.l_accessories_1,
            avatar.l_accessories_2,
            avatar.l_accessories_3,
            avatar.l_accessories_4,
            avatar.l_accessories_5,
            avatar.l_accessories_6,
            avatar.l_accessories_7,
            avatar.l_accessories_8,
        ],
    );

    // Emotion layers (12 emotions × 9 positions)
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_neutral_0,
            avatar.e_neutral_1,
            avatar.e_neutral_2,
            avatar.e_neutral_3,
            avatar.e_neutral_4,
            avatar.e_neutral_5,
            avatar.e_neutral_6,
            avatar.e_neutral_7,
            avatar.e_neutral_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_happy_0,
            avatar.e_happy_1,
            avatar.e_happy_2,
            avatar.e_happy_3,
            avatar.e_happy_4,
            avatar.e_happy_5,
            avatar.e_happy_6,
            avatar.e_happy_7,
            avatar.e_happy_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_sad_0,
            avatar.e_sad_1,
            avatar.e_sad_2,
            avatar.e_sad_3,
            avatar.e_sad_4,
            avatar.e_sad_5,
            avatar.e_sad_6,
            avatar.e_sad_7,
            avatar.e_sad_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_angry_0,
            avatar.e_angry_1,
            avatar.e_angry_2,
            avatar.e_angry_3,
            avatar.e_angry_4,
            avatar.e_angry_5,
            avatar.e_angry_6,
            avatar.e_angry_7,
            avatar.e_angry_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_surprised_0,
            avatar.e_surprised_1,
            avatar.e_surprised_2,
            avatar.e_surprised_3,
            avatar.e_surprised_4,
            avatar.e_surprised_5,
            avatar.e_surprised_6,
            avatar.e_surprised_7,
            avatar.e_surprised_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_thinking_0,
            avatar.e_thinking_1,
            avatar.e_thinking_2,
            avatar.e_thinking_3,
            avatar.e_thinking_4,
            avatar.e_thinking_5,
            avatar.e_thinking_6,
            avatar.e_thinking_7,
            avatar.e_thinking_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_laughing_0,
            avatar.e_laughing_1,
            avatar.e_laughing_2,
            avatar.e_laughing_3,
            avatar.e_laughing_4,
            avatar.e_laughing_5,
            avatar.e_laughing_6,
            avatar.e_laughing_7,
            avatar.e_laughing_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_crying_0,
            avatar.e_crying_1,
            avatar.e_crying_2,
            avatar.e_crying_3,
            avatar.e_crying_4,
            avatar.e_crying_5,
            avatar.e_crying_6,
            avatar.e_crying_7,
            avatar.e_crying_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_love_0,
            avatar.e_love_1,
            avatar.e_love_2,
            avatar.e_love_3,
            avatar.e_love_4,
            avatar.e_love_5,
            avatar.e_love_6,
            avatar.e_love_7,
            avatar.e_love_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_confused_0,
            avatar.e_confused_1,
            avatar.e_confused_2,
            avatar.e_confused_3,
            avatar.e_confused_4,
            avatar.e_confused_5,
            avatar.e_confused_6,
            avatar.e_confused_7,
            avatar.e_confused_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_sleeping_0,
            avatar.e_sleeping_1,
            avatar.e_sleeping_2,
            avatar.e_sleeping_3,
            avatar.e_sleeping_4,
            avatar.e_sleeping_5,
            avatar.e_sleeping_6,
            avatar.e_sleeping_7,
            avatar.e_sleeping_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_wink_0,
            avatar.e_wink_1,
            avatar.e_wink_2,
            avatar.e_wink_3,
            avatar.e_wink_4,
            avatar.e_wink_5,
            avatar.e_wink_6,
            avatar.e_wink_7,
            avatar.e_wink_8,
        ],
    );

    // Query 2: Bulk resolve all UUIDs to paths
    let paths: HashMap<Uuid, String> = if uuids.is_empty() {
        HashMap::new()
    } else {
        sqlx::query_as::<_, (Uuid, String)>(
            "SELECT id, prop_asset_path FROM auth.inventory WHERE id = ANY($1)",
        )
        .bind(&uuids)
        .fetch_all(pool)
        .await?
        .into_iter()
        .collect()
    };

    // Build the AvatarWithPaths
    let resolve =
        |uuid: Option<Uuid>| -> Option<String> { uuid.and_then(|id| paths.get(&id).cloned()) };

    // Check if any UUID in the array is non-null (emotion is available)
    let has_any = |slots: &[Option<Uuid>]| -> bool { slots.iter().any(|u| u.is_some()) };

    // Compute emotions_available from UUID presence (not path resolution)
    let emotions_available = [
        has_any(&[
            avatar.e_neutral_0,
            avatar.e_neutral_1,
            avatar.e_neutral_2,
            avatar.e_neutral_3,
            avatar.e_neutral_4,
            avatar.e_neutral_5,
            avatar.e_neutral_6,
            avatar.e_neutral_7,
            avatar.e_neutral_8,
        ]),
        has_any(&[
            avatar.e_happy_0,
            avatar.e_happy_1,
            avatar.e_happy_2,
            avatar.e_happy_3,
            avatar.e_happy_4,
            avatar.e_happy_5,
            avatar.e_happy_6,
            avatar.e_happy_7,
            avatar.e_happy_8,
        ]),
        has_any(&[
            avatar.e_sad_0,
            avatar.e_sad_1,
            avatar.e_sad_2,
            avatar.e_sad_3,
            avatar.e_sad_4,
            avatar.e_sad_5,
            avatar.e_sad_6,
            avatar.e_sad_7,
            avatar.e_sad_8,
        ]),
        has_any(&[
            avatar.e_angry_0,
            avatar.e_angry_1,
            avatar.e_angry_2,
            avatar.e_angry_3,
            avatar.e_angry_4,
            avatar.e_angry_5,
            avatar.e_angry_6,
            avatar.e_angry_7,
            avatar.e_angry_8,
        ]),
        has_any(&[
            avatar.e_surprised_0,
            avatar.e_surprised_1,
            avatar.e_surprised_2,
            avatar.e_surprised_3,
            avatar.e_surprised_4,
            avatar.e_surprised_5,
            avatar.e_surprised_6,
            avatar.e_surprised_7,
            avatar.e_surprised_8,
        ]),
        has_any(&[
            avatar.e_thinking_0,
            avatar.e_thinking_1,
            avatar.e_thinking_2,
            avatar.e_thinking_3,
            avatar.e_thinking_4,
            avatar.e_thinking_5,
            avatar.e_thinking_6,
            avatar.e_thinking_7,
            avatar.e_thinking_8,
        ]),
        has_any(&[
            avatar.e_laughing_0,
            avatar.e_laughing_1,
            avatar.e_laughing_2,
            avatar.e_laughing_3,
            avatar.e_laughing_4,
            avatar.e_laughing_5,
            avatar.e_laughing_6,
            avatar.e_laughing_7,
            avatar.e_laughing_8,
        ]),
        has_any(&[
            avatar.e_crying_0,
            avatar.e_crying_1,
            avatar.e_crying_2,
            avatar.e_crying_3,
            avatar.e_crying_4,
            avatar.e_crying_5,
            avatar.e_crying_6,
            avatar.e_crying_7,
            avatar.e_crying_8,
        ]),
        has_any(&[
            avatar.e_love_0,
            avatar.e_love_1,
            avatar.e_love_2,
            avatar.e_love_3,
            avatar.e_love_4,
            avatar.e_love_5,
            avatar.e_love_6,
            avatar.e_love_7,
            avatar.e_love_8,
        ]),
        has_any(&[
            avatar.e_confused_0,
            avatar.e_confused_1,
            avatar.e_confused_2,
            avatar.e_confused_3,
            avatar.e_confused_4,
            avatar.e_confused_5,
            avatar.e_confused_6,
            avatar.e_confused_7,
            avatar.e_confused_8,
        ]),
        has_any(&[
            avatar.e_sleeping_0,
            avatar.e_sleeping_1,
            avatar.e_sleeping_2,
            avatar.e_sleeping_3,
            avatar.e_sleeping_4,
            avatar.e_sleeping_5,
            avatar.e_sleeping_6,
            avatar.e_sleeping_7,
            avatar.e_sleeping_8,
        ]),
        has_any(&[
            avatar.e_wink_0,
            avatar.e_wink_1,
            avatar.e_wink_2,
            avatar.e_wink_3,
            avatar.e_wink_4,
            avatar.e_wink_5,
            avatar.e_wink_6,
            avatar.e_wink_7,
            avatar.e_wink_8,
        ]),
    ];

    Ok(Some(AvatarWithPaths {
        avatar_id: avatar.id,
        current_emotion: avatar.current_emotion,
        skin_layer: [
            resolve(avatar.l_skin_0),
            resolve(avatar.l_skin_1),
            resolve(avatar.l_skin_2),
            resolve(avatar.l_skin_3),
            resolve(avatar.l_skin_4),
            resolve(avatar.l_skin_5),
            resolve(avatar.l_skin_6),
            resolve(avatar.l_skin_7),
            resolve(avatar.l_skin_8),
        ],
        clothes_layer: [
            resolve(avatar.l_clothes_0),
            resolve(avatar.l_clothes_1),
            resolve(avatar.l_clothes_2),
            resolve(avatar.l_clothes_3),
            resolve(avatar.l_clothes_4),
            resolve(avatar.l_clothes_5),
            resolve(avatar.l_clothes_6),
            resolve(avatar.l_clothes_7),
            resolve(avatar.l_clothes_8),
        ],
        accessories_layer: [
            resolve(avatar.l_accessories_0),
            resolve(avatar.l_accessories_1),
            resolve(avatar.l_accessories_2),
            resolve(avatar.l_accessories_3),
            resolve(avatar.l_accessories_4),
            resolve(avatar.l_accessories_5),
            resolve(avatar.l_accessories_6),
            resolve(avatar.l_accessories_7),
            resolve(avatar.l_accessories_8),
        ],
        emotions: [
            // Neutral (0)
            [
                resolve(avatar.e_neutral_0),
                resolve(avatar.e_neutral_1),
                resolve(avatar.e_neutral_2),
                resolve(avatar.e_neutral_3),
                resolve(avatar.e_neutral_4),
                resolve(avatar.e_neutral_5),
                resolve(avatar.e_neutral_6),
                resolve(avatar.e_neutral_7),
                resolve(avatar.e_neutral_8),
            ],
            // Happy (1)
            [
                resolve(avatar.e_happy_0),
                resolve(avatar.e_happy_1),
                resolve(avatar.e_happy_2),
                resolve(avatar.e_happy_3),
                resolve(avatar.e_happy_4),
                resolve(avatar.e_happy_5),
                resolve(avatar.e_happy_6),
                resolve(avatar.e_happy_7),
                resolve(avatar.e_happy_8),
            ],
            // Sad (2)
            [
                resolve(avatar.e_sad_0),
                resolve(avatar.e_sad_1),
                resolve(avatar.e_sad_2),
                resolve(avatar.e_sad_3),
                resolve(avatar.e_sad_4),
                resolve(avatar.e_sad_5),
                resolve(avatar.e_sad_6),
                resolve(avatar.e_sad_7),
                resolve(avatar.e_sad_8),
            ],
            // Angry (3)
            [
                resolve(avatar.e_angry_0),
                resolve(avatar.e_angry_1),
                resolve(avatar.e_angry_2),
                resolve(avatar.e_angry_3),
                resolve(avatar.e_angry_4),
                resolve(avatar.e_angry_5),
                resolve(avatar.e_angry_6),
                resolve(avatar.e_angry_7),
                resolve(avatar.e_angry_8),
            ],
            // Surprised (4)
            [
                resolve(avatar.e_surprised_0),
                resolve(avatar.e_surprised_1),
                resolve(avatar.e_surprised_2),
                resolve(avatar.e_surprised_3),
                resolve(avatar.e_surprised_4),
                resolve(avatar.e_surprised_5),
                resolve(avatar.e_surprised_6),
                resolve(avatar.e_surprised_7),
                resolve(avatar.e_surprised_8),
            ],
            // Thinking (5)
            [
                resolve(avatar.e_thinking_0),
                resolve(avatar.e_thinking_1),
                resolve(avatar.e_thinking_2),
                resolve(avatar.e_thinking_3),
                resolve(avatar.e_thinking_4),
                resolve(avatar.e_thinking_5),
                resolve(avatar.e_thinking_6),
                resolve(avatar.e_thinking_7),
                resolve(avatar.e_thinking_8),
            ],
            // Laughing (6)
            [
                resolve(avatar.e_laughing_0),
                resolve(avatar.e_laughing_1),
                resolve(avatar.e_laughing_2),
                resolve(avatar.e_laughing_3),
                resolve(avatar.e_laughing_4),
                resolve(avatar.e_laughing_5),
                resolve(avatar.e_laughing_6),
                resolve(avatar.e_laughing_7),
                resolve(avatar.e_laughing_8),
            ],
            // Crying (7)
            [
                resolve(avatar.e_crying_0),
                resolve(avatar.e_crying_1),
                resolve(avatar.e_crying_2),
                resolve(avatar.e_crying_3),
                resolve(avatar.e_crying_4),
                resolve(avatar.e_crying_5),
                resolve(avatar.e_crying_6),
                resolve(avatar.e_crying_7),
                resolve(avatar.e_crying_8),
            ],
            // Love (8)
            [
                resolve(avatar.e_love_0),
                resolve(avatar.e_love_1),
                resolve(avatar.e_love_2),
                resolve(avatar.e_love_3),
                resolve(avatar.e_love_4),
                resolve(avatar.e_love_5),
                resolve(avatar.e_love_6),
                resolve(avatar.e_love_7),
                resolve(avatar.e_love_8),
            ],
            // Confused (9)
            [
                resolve(avatar.e_confused_0),
                resolve(avatar.e_confused_1),
                resolve(avatar.e_confused_2),
                resolve(avatar.e_confused_3),
                resolve(avatar.e_confused_4),
                resolve(avatar.e_confused_5),
                resolve(avatar.e_confused_6),
                resolve(avatar.e_confused_7),
                resolve(avatar.e_confused_8),
            ],
            // Sleeping (10)
            [
                resolve(avatar.e_sleeping_0),
                resolve(avatar.e_sleeping_1),
                resolve(avatar.e_sleeping_2),
                resolve(avatar.e_sleeping_3),
                resolve(avatar.e_sleeping_4),
                resolve(avatar.e_sleeping_5),
                resolve(avatar.e_sleeping_6),
                resolve(avatar.e_sleeping_7),
                resolve(avatar.e_sleeping_8),
            ],
            // Wink (11)
            [
                resolve(avatar.e_wink_0),
                resolve(avatar.e_wink_1),
                resolve(avatar.e_wink_2),
                resolve(avatar.e_wink_3),
                resolve(avatar.e_wink_4),
                resolve(avatar.e_wink_5),
                resolve(avatar.e_wink_6),
                resolve(avatar.e_wink_7),
                resolve(avatar.e_wink_8),
            ],
        ],
        emotions_available,
    }))
}

/// Get full avatar with all inventory UUIDs resolved to asset paths (connection variant).
///
/// This variant accepts a mutable connection reference for use with RLS-enabled connections.
pub async fn get_avatar_with_paths_conn(
    conn: &mut PgConnection,
    user_id: Uuid,
    realm_id: Uuid,
) -> Result<Option<AvatarWithPaths>, AppError> {
    // Query 1: Get the avatar row with current_emotion from active_avatars
    let avatar_row = sqlx::query_as::<_, AvatarWithEmotion>(
        r#"
        SELECT
            a.*,
            aa.current_emotion
        FROM auth.active_avatars aa
        JOIN auth.avatars a ON aa.avatar_id = a.id
        WHERE aa.user_id = $1 AND aa.realm_id = $2
        "#,
    )
    .bind(user_id)
    .bind(realm_id)
    .fetch_optional(&mut *conn)
    .await?;

    let Some(avatar) = avatar_row else {
        return Ok(None);
    };

    // Collect all non-null UUIDs from the avatar slots
    let mut uuids: Vec<Uuid> = Vec::new();

    // Content layers
    collect_uuids(
        &mut uuids,
        &[
            avatar.l_skin_0,
            avatar.l_skin_1,
            avatar.l_skin_2,
            avatar.l_skin_3,
            avatar.l_skin_4,
            avatar.l_skin_5,
            avatar.l_skin_6,
            avatar.l_skin_7,
            avatar.l_skin_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.l_clothes_0,
            avatar.l_clothes_1,
            avatar.l_clothes_2,
            avatar.l_clothes_3,
            avatar.l_clothes_4,
            avatar.l_clothes_5,
            avatar.l_clothes_6,
            avatar.l_clothes_7,
            avatar.l_clothes_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.l_accessories_0,
            avatar.l_accessories_1,
            avatar.l_accessories_2,
            avatar.l_accessories_3,
            avatar.l_accessories_4,
            avatar.l_accessories_5,
            avatar.l_accessories_6,
            avatar.l_accessories_7,
            avatar.l_accessories_8,
        ],
    );

    // Emotion layers (12 emotions × 9 positions)
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_neutral_0,
            avatar.e_neutral_1,
            avatar.e_neutral_2,
            avatar.e_neutral_3,
            avatar.e_neutral_4,
            avatar.e_neutral_5,
            avatar.e_neutral_6,
            avatar.e_neutral_7,
            avatar.e_neutral_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_happy_0,
            avatar.e_happy_1,
            avatar.e_happy_2,
            avatar.e_happy_3,
            avatar.e_happy_4,
            avatar.e_happy_5,
            avatar.e_happy_6,
            avatar.e_happy_7,
            avatar.e_happy_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_sad_0,
            avatar.e_sad_1,
            avatar.e_sad_2,
            avatar.e_sad_3,
            avatar.e_sad_4,
            avatar.e_sad_5,
            avatar.e_sad_6,
            avatar.e_sad_7,
            avatar.e_sad_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_angry_0,
            avatar.e_angry_1,
            avatar.e_angry_2,
            avatar.e_angry_3,
            avatar.e_angry_4,
            avatar.e_angry_5,
            avatar.e_angry_6,
            avatar.e_angry_7,
            avatar.e_angry_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_surprised_0,
            avatar.e_surprised_1,
            avatar.e_surprised_2,
            avatar.e_surprised_3,
            avatar.e_surprised_4,
            avatar.e_surprised_5,
            avatar.e_surprised_6,
            avatar.e_surprised_7,
            avatar.e_surprised_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_thinking_0,
            avatar.e_thinking_1,
            avatar.e_thinking_2,
            avatar.e_thinking_3,
            avatar.e_thinking_4,
            avatar.e_thinking_5,
            avatar.e_thinking_6,
            avatar.e_thinking_7,
            avatar.e_thinking_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_laughing_0,
            avatar.e_laughing_1,
            avatar.e_laughing_2,
            avatar.e_laughing_3,
            avatar.e_laughing_4,
            avatar.e_laughing_5,
            avatar.e_laughing_6,
            avatar.e_laughing_7,
            avatar.e_laughing_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_crying_0,
            avatar.e_crying_1,
            avatar.e_crying_2,
            avatar.e_crying_3,
            avatar.e_crying_4,
            avatar.e_crying_5,
            avatar.e_crying_6,
            avatar.e_crying_7,
            avatar.e_crying_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_love_0,
            avatar.e_love_1,
            avatar.e_love_2,
            avatar.e_love_3,
            avatar.e_love_4,
            avatar.e_love_5,
            avatar.e_love_6,
            avatar.e_love_7,
            avatar.e_love_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_confused_0,
            avatar.e_confused_1,
            avatar.e_confused_2,
            avatar.e_confused_3,
            avatar.e_confused_4,
            avatar.e_confused_5,
            avatar.e_confused_6,
            avatar.e_confused_7,
            avatar.e_confused_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_sleeping_0,
            avatar.e_sleeping_1,
            avatar.e_sleeping_2,
            avatar.e_sleeping_3,
            avatar.e_sleeping_4,
            avatar.e_sleeping_5,
            avatar.e_sleeping_6,
            avatar.e_sleeping_7,
            avatar.e_sleeping_8,
        ],
    );
    collect_uuids(
        &mut uuids,
        &[
            avatar.e_wink_0,
            avatar.e_wink_1,
            avatar.e_wink_2,
            avatar.e_wink_3,
            avatar.e_wink_4,
            avatar.e_wink_5,
            avatar.e_wink_6,
            avatar.e_wink_7,
            avatar.e_wink_8,
        ],
    );

    // Query 2: Bulk resolve all UUIDs to paths
    let paths: HashMap<Uuid, String> = if uuids.is_empty() {
        HashMap::new()
    } else {
        sqlx::query_as::<_, (Uuid, String)>(
            "SELECT id, prop_asset_path FROM auth.inventory WHERE id = ANY($1)",
        )
        .bind(&uuids)
        .fetch_all(&mut *conn)
        .await?
        .into_iter()
        .collect()
    };

    // Build the AvatarWithPaths
    let resolve =
        |uuid: Option<Uuid>| -> Option<String> { uuid.and_then(|id| paths.get(&id).cloned()) };

    // Check if any UUID in the array is non-null (emotion is available)
    let has_any = |slots: &[Option<Uuid>]| -> bool { slots.iter().any(|u| u.is_some()) };

    // Compute emotions_available from UUID presence (not path resolution)
    let emotions_available = [
        has_any(&[
            avatar.e_neutral_0,
            avatar.e_neutral_1,
            avatar.e_neutral_2,
            avatar.e_neutral_3,
            avatar.e_neutral_4,
            avatar.e_neutral_5,
            avatar.e_neutral_6,
            avatar.e_neutral_7,
            avatar.e_neutral_8,
        ]),
        has_any(&[
            avatar.e_happy_0,
            avatar.e_happy_1,
            avatar.e_happy_2,
            avatar.e_happy_3,
            avatar.e_happy_4,
            avatar.e_happy_5,
            avatar.e_happy_6,
            avatar.e_happy_7,
            avatar.e_happy_8,
        ]),
        has_any(&[
            avatar.e_sad_0,
            avatar.e_sad_1,
            avatar.e_sad_2,
            avatar.e_sad_3,
            avatar.e_sad_4,
            avatar.e_sad_5,
            avatar.e_sad_6,
            avatar.e_sad_7,
            avatar.e_sad_8,
        ]),
        has_any(&[
            avatar.e_angry_0,
            avatar.e_angry_1,
            avatar.e_angry_2,
            avatar.e_angry_3,
            avatar.e_angry_4,
            avatar.e_angry_5,
            avatar.e_angry_6,
            avatar.e_angry_7,
            avatar.e_angry_8,
        ]),
        has_any(&[
            avatar.e_surprised_0,
            avatar.e_surprised_1,
            avatar.e_surprised_2,
            avatar.e_surprised_3,
            avatar.e_surprised_4,
            avatar.e_surprised_5,
            avatar.e_surprised_6,
            avatar.e_surprised_7,
            avatar.e_surprised_8,
        ]),
        has_any(&[
            avatar.e_thinking_0,
            avatar.e_thinking_1,
            avatar.e_thinking_2,
            avatar.e_thinking_3,
            avatar.e_thinking_4,
            avatar.e_thinking_5,
            avatar.e_thinking_6,
            avatar.e_thinking_7,
            avatar.e_thinking_8,
        ]),
        has_any(&[
            avatar.e_laughing_0,
            avatar.e_laughing_1,
            avatar.e_laughing_2,
            avatar.e_laughing_3,
            avatar.e_laughing_4,
            avatar.e_laughing_5,
            avatar.e_laughing_6,
            avatar.e_laughing_7,
            avatar.e_laughing_8,
        ]),
        has_any(&[
            avatar.e_crying_0,
            avatar.e_crying_1,
            avatar.e_crying_2,
            avatar.e_crying_3,
            avatar.e_crying_4,
            avatar.e_crying_5,
            avatar.e_crying_6,
            avatar.e_crying_7,
            avatar.e_crying_8,
        ]),
        has_any(&[
            avatar.e_love_0,
            avatar.e_love_1,
            avatar.e_love_2,
            avatar.e_love_3,
            avatar.e_love_4,
            avatar.e_love_5,
            avatar.e_love_6,
            avatar.e_love_7,
            avatar.e_love_8,
        ]),
        has_any(&[
            avatar.e_confused_0,
            avatar.e_confused_1,
            avatar.e_confused_2,
            avatar.e_confused_3,
            avatar.e_confused_4,
            avatar.e_confused_5,
            avatar.e_confused_6,
            avatar.e_confused_7,
            avatar.e_confused_8,
        ]),
        has_any(&[
            avatar.e_sleeping_0,
            avatar.e_sleeping_1,
            avatar.e_sleeping_2,
            avatar.e_sleeping_3,
            avatar.e_sleeping_4,
            avatar.e_sleeping_5,
            avatar.e_sleeping_6,
            avatar.e_sleeping_7,
            avatar.e_sleeping_8,
        ]),
        has_any(&[
            avatar.e_wink_0,
            avatar.e_wink_1,
            avatar.e_wink_2,
            avatar.e_wink_3,
            avatar.e_wink_4,
            avatar.e_wink_5,
            avatar.e_wink_6,
            avatar.e_wink_7,
            avatar.e_wink_8,
        ]),
    ];

    Ok(Some(AvatarWithPaths {
        avatar_id: avatar.id,
        current_emotion: avatar.current_emotion,
        skin_layer: [
            resolve(avatar.l_skin_0),
            resolve(avatar.l_skin_1),
            resolve(avatar.l_skin_2),
            resolve(avatar.l_skin_3),
            resolve(avatar.l_skin_4),
            resolve(avatar.l_skin_5),
            resolve(avatar.l_skin_6),
            resolve(avatar.l_skin_7),
            resolve(avatar.l_skin_8),
        ],
        clothes_layer: [
            resolve(avatar.l_clothes_0),
            resolve(avatar.l_clothes_1),
            resolve(avatar.l_clothes_2),
            resolve(avatar.l_clothes_3),
            resolve(avatar.l_clothes_4),
            resolve(avatar.l_clothes_5),
            resolve(avatar.l_clothes_6),
            resolve(avatar.l_clothes_7),
            resolve(avatar.l_clothes_8),
        ],
        accessories_layer: [
            resolve(avatar.l_accessories_0),
            resolve(avatar.l_accessories_1),
            resolve(avatar.l_accessories_2),
            resolve(avatar.l_accessories_3),
            resolve(avatar.l_accessories_4),
            resolve(avatar.l_accessories_5),
            resolve(avatar.l_accessories_6),
            resolve(avatar.l_accessories_7),
            resolve(avatar.l_accessories_8),
        ],
        emotions: [
            // Neutral (0)
            [
                resolve(avatar.e_neutral_0),
                resolve(avatar.e_neutral_1),
                resolve(avatar.e_neutral_2),
                resolve(avatar.e_neutral_3),
                resolve(avatar.e_neutral_4),
                resolve(avatar.e_neutral_5),
                resolve(avatar.e_neutral_6),
                resolve(avatar.e_neutral_7),
                resolve(avatar.e_neutral_8),
            ],
            // Happy (1)
            [
                resolve(avatar.e_happy_0),
                resolve(avatar.e_happy_1),
                resolve(avatar.e_happy_2),
                resolve(avatar.e_happy_3),
                resolve(avatar.e_happy_4),
                resolve(avatar.e_happy_5),
                resolve(avatar.e_happy_6),
                resolve(avatar.e_happy_7),
                resolve(avatar.e_happy_8),
            ],
            // Sad (2)
            [
                resolve(avatar.e_sad_0),
                resolve(avatar.e_sad_1),
                resolve(avatar.e_sad_2),
                resolve(avatar.e_sad_3),
                resolve(avatar.e_sad_4),
                resolve(avatar.e_sad_5),
                resolve(avatar.e_sad_6),
                resolve(avatar.e_sad_7),
                resolve(avatar.e_sad_8),
            ],
            // Angry (3)
            [
                resolve(avatar.e_angry_0),
                resolve(avatar.e_angry_1),
                resolve(avatar.e_angry_2),
                resolve(avatar.e_angry_3),
                resolve(avatar.e_angry_4),
                resolve(avatar.e_angry_5),
                resolve(avatar.e_angry_6),
                resolve(avatar.e_angry_7),
                resolve(avatar.e_angry_8),
            ],
            // Surprised (4)
            [
                resolve(avatar.e_surprised_0),
                resolve(avatar.e_surprised_1),
                resolve(avatar.e_surprised_2),
                resolve(avatar.e_surprised_3),
                resolve(avatar.e_surprised_4),
                resolve(avatar.e_surprised_5),
                resolve(avatar.e_surprised_6),
                resolve(avatar.e_surprised_7),
                resolve(avatar.e_surprised_8),
            ],
            // Thinking (5)
            [
                resolve(avatar.e_thinking_0),
                resolve(avatar.e_thinking_1),
                resolve(avatar.e_thinking_2),
                resolve(avatar.e_thinking_3),
                resolve(avatar.e_thinking_4),
                resolve(avatar.e_thinking_5),
                resolve(avatar.e_thinking_6),
                resolve(avatar.e_thinking_7),
                resolve(avatar.e_thinking_8),
            ],
            // Laughing (6)
            [
                resolve(avatar.e_laughing_0),
                resolve(avatar.e_laughing_1),
                resolve(avatar.e_laughing_2),
                resolve(avatar.e_laughing_3),
                resolve(avatar.e_laughing_4),
                resolve(avatar.e_laughing_5),
                resolve(avatar.e_laughing_6),
                resolve(avatar.e_laughing_7),
                resolve(avatar.e_laughing_8),
            ],
            // Crying (7)
            [
                resolve(avatar.e_crying_0),
                resolve(avatar.e_crying_1),
                resolve(avatar.e_crying_2),
                resolve(avatar.e_crying_3),
                resolve(avatar.e_crying_4),
                resolve(avatar.e_crying_5),
                resolve(avatar.e_crying_6),
                resolve(avatar.e_crying_7),
                resolve(avatar.e_crying_8),
            ],
            // Love (8)
            [
                resolve(avatar.e_love_0),
                resolve(avatar.e_love_1),
                resolve(avatar.e_love_2),
                resolve(avatar.e_love_3),
                resolve(avatar.e_love_4),
                resolve(avatar.e_love_5),
                resolve(avatar.e_love_6),
                resolve(avatar.e_love_7),
                resolve(avatar.e_love_8),
            ],
            // Confused (9)
            [
                resolve(avatar.e_confused_0),
                resolve(avatar.e_confused_1),
                resolve(avatar.e_confused_2),
                resolve(avatar.e_confused_3),
                resolve(avatar.e_confused_4),
                resolve(avatar.e_confused_5),
                resolve(avatar.e_confused_6),
                resolve(avatar.e_confused_7),
                resolve(avatar.e_confused_8),
            ],
            // Sleeping (10)
            [
                resolve(avatar.e_sleeping_0),
                resolve(avatar.e_sleeping_1),
                resolve(avatar.e_sleeping_2),
                resolve(avatar.e_sleeping_3),
                resolve(avatar.e_sleeping_4),
                resolve(avatar.e_sleeping_5),
                resolve(avatar.e_sleeping_6),
                resolve(avatar.e_sleeping_7),
                resolve(avatar.e_sleeping_8),
            ],
            // Wink (11)
            [
                resolve(avatar.e_wink_0),
                resolve(avatar.e_wink_1),
                resolve(avatar.e_wink_2),
                resolve(avatar.e_wink_3),
                resolve(avatar.e_wink_4),
                resolve(avatar.e_wink_5),
                resolve(avatar.e_wink_6),
                resolve(avatar.e_wink_7),
                resolve(avatar.e_wink_8),
            ],
        ],
        emotions_available,
    }))
}

/// Helper to collect non-null UUIDs into a Vec.
fn collect_uuids(dest: &mut Vec<Uuid>, sources: &[Option<Uuid>]) {
    for uuid in sources {
        if let Some(id) = uuid {
            dest.push(*id);
        }
    }
}

/// Avatar row with current_emotion from active_avatars join.
#[derive(Debug, sqlx::FromRow)]
struct AvatarWithEmotion {
    pub id: Uuid,
    pub current_emotion: i16,
    // Content layers
    pub l_skin_0: Option<Uuid>,
    pub l_skin_1: Option<Uuid>,
    pub l_skin_2: Option<Uuid>,
    pub l_skin_3: Option<Uuid>,
    pub l_skin_4: Option<Uuid>,
    pub l_skin_5: Option<Uuid>,
    pub l_skin_6: Option<Uuid>,
    pub l_skin_7: Option<Uuid>,
    pub l_skin_8: Option<Uuid>,
    pub l_clothes_0: Option<Uuid>,
    pub l_clothes_1: Option<Uuid>,
    pub l_clothes_2: Option<Uuid>,
    pub l_clothes_3: Option<Uuid>,
    pub l_clothes_4: Option<Uuid>,
    pub l_clothes_5: Option<Uuid>,
    pub l_clothes_6: Option<Uuid>,
    pub l_clothes_7: Option<Uuid>,
    pub l_clothes_8: Option<Uuid>,
    pub l_accessories_0: Option<Uuid>,
    pub l_accessories_1: Option<Uuid>,
    pub l_accessories_2: Option<Uuid>,
    pub l_accessories_3: Option<Uuid>,
    pub l_accessories_4: Option<Uuid>,
    pub l_accessories_5: Option<Uuid>,
    pub l_accessories_6: Option<Uuid>,
    pub l_accessories_7: Option<Uuid>,
    pub l_accessories_8: Option<Uuid>,
    // Emotion layers
    pub e_neutral_0: Option<Uuid>,
    pub e_neutral_1: Option<Uuid>,
    pub e_neutral_2: Option<Uuid>,
    pub e_neutral_3: Option<Uuid>,
    pub e_neutral_4: Option<Uuid>,
    pub e_neutral_5: Option<Uuid>,
    pub e_neutral_6: Option<Uuid>,
    pub e_neutral_7: Option<Uuid>,
    pub e_neutral_8: Option<Uuid>,
    pub e_happy_0: Option<Uuid>,
    pub e_happy_1: Option<Uuid>,
    pub e_happy_2: Option<Uuid>,
    pub e_happy_3: Option<Uuid>,
    pub e_happy_4: Option<Uuid>,
    pub e_happy_5: Option<Uuid>,
    pub e_happy_6: Option<Uuid>,
    pub e_happy_7: Option<Uuid>,
    pub e_happy_8: Option<Uuid>,
    pub e_sad_0: Option<Uuid>,
    pub e_sad_1: Option<Uuid>,
    pub e_sad_2: Option<Uuid>,
    pub e_sad_3: Option<Uuid>,
    pub e_sad_4: Option<Uuid>,
    pub e_sad_5: Option<Uuid>,
    pub e_sad_6: Option<Uuid>,
    pub e_sad_7: Option<Uuid>,
    pub e_sad_8: Option<Uuid>,
    pub e_angry_0: Option<Uuid>,
    pub e_angry_1: Option<Uuid>,
    pub e_angry_2: Option<Uuid>,
    pub e_angry_3: Option<Uuid>,
    pub e_angry_4: Option<Uuid>,
    pub e_angry_5: Option<Uuid>,
    pub e_angry_6: Option<Uuid>,
    pub e_angry_7: Option<Uuid>,
    pub e_angry_8: Option<Uuid>,
    pub e_surprised_0: Option<Uuid>,
    pub e_surprised_1: Option<Uuid>,
    pub e_surprised_2: Option<Uuid>,
    pub e_surprised_3: Option<Uuid>,
    pub e_surprised_4: Option<Uuid>,
    pub e_surprised_5: Option<Uuid>,
    pub e_surprised_6: Option<Uuid>,
    pub e_surprised_7: Option<Uuid>,
    pub e_surprised_8: Option<Uuid>,
    pub e_thinking_0: Option<Uuid>,
    pub e_thinking_1: Option<Uuid>,
    pub e_thinking_2: Option<Uuid>,
    pub e_thinking_3: Option<Uuid>,
    pub e_thinking_4: Option<Uuid>,
    pub e_thinking_5: Option<Uuid>,
    pub e_thinking_6: Option<Uuid>,
    pub e_thinking_7: Option<Uuid>,
    pub e_thinking_8: Option<Uuid>,
    pub e_laughing_0: Option<Uuid>,
    pub e_laughing_1: Option<Uuid>,
    pub e_laughing_2: Option<Uuid>,
    pub e_laughing_3: Option<Uuid>,
    pub e_laughing_4: Option<Uuid>,
    pub e_laughing_5: Option<Uuid>,
    pub e_laughing_6: Option<Uuid>,
    pub e_laughing_7: Option<Uuid>,
    pub e_laughing_8: Option<Uuid>,
    pub e_crying_0: Option<Uuid>,
    pub e_crying_1: Option<Uuid>,
    pub e_crying_2: Option<Uuid>,
    pub e_crying_3: Option<Uuid>,
    pub e_crying_4: Option<Uuid>,
    pub e_crying_5: Option<Uuid>,
    pub e_crying_6: Option<Uuid>,
    pub e_crying_7: Option<Uuid>,
    pub e_crying_8: Option<Uuid>,
    pub e_love_0: Option<Uuid>,
    pub e_love_1: Option<Uuid>,
    pub e_love_2: Option<Uuid>,
    pub e_love_3: Option<Uuid>,
    pub e_love_4: Option<Uuid>,
    pub e_love_5: Option<Uuid>,
    pub e_love_6: Option<Uuid>,
    pub e_love_7: Option<Uuid>,
    pub e_love_8: Option<Uuid>,
    pub e_confused_0: Option<Uuid>,
    pub e_confused_1: Option<Uuid>,
    pub e_confused_2: Option<Uuid>,
    pub e_confused_3: Option<Uuid>,
    pub e_confused_4: Option<Uuid>,
    pub e_confused_5: Option<Uuid>,
    pub e_confused_6: Option<Uuid>,
    pub e_confused_7: Option<Uuid>,
    pub e_confused_8: Option<Uuid>,
    pub e_sleeping_0: Option<Uuid>,
    pub e_sleeping_1: Option<Uuid>,
    pub e_sleeping_2: Option<Uuid>,
    pub e_sleeping_3: Option<Uuid>,
    pub e_sleeping_4: Option<Uuid>,
    pub e_sleeping_5: Option<Uuid>,
    pub e_sleeping_6: Option<Uuid>,
    pub e_sleeping_7: Option<Uuid>,
    pub e_sleeping_8: Option<Uuid>,
    pub e_wink_0: Option<Uuid>,
    pub e_wink_1: Option<Uuid>,
    pub e_wink_2: Option<Uuid>,
    pub e_wink_3: Option<Uuid>,
    pub e_wink_4: Option<Uuid>,
    pub e_wink_5: Option<Uuid>,
    pub e_wink_6: Option<Uuid>,
    pub e_wink_7: Option<Uuid>,
    pub e_wink_8: Option<Uuid>,
}

/// Set the current emotion for a user (simplified - no path lookup).
///
/// This is used when the user's client has the full avatar cached locally
/// and can render the emotion from its local cache.
pub async fn set_emotion_simple<'e>(
    executor: impl PgExecutor<'e>,
    user_id: Uuid,
    realm_id: Uuid,
    emotion: i16,
) -> Result<(), AppError> {
    if emotion < 0 || emotion > 11 {
        return Err(AppError::Validation("Emotion must be 0-11".to_string()));
    }

    let result = sqlx::query(
        r#"
        UPDATE auth.active_avatars
        SET current_emotion = $3, updated_at = now()
        WHERE user_id = $1 AND realm_id = $2
        "#,
    )
    .bind(user_id)
    .bind(realm_id)
    .bind(emotion)
    .execute(executor)
    .await?;

    if result.rows_affected() == 0 {
        return Err(AppError::NotFound(
            "No active avatar for this user in this realm".to_string(),
        ));
    }

    Ok(())
}

/// Update an avatar slot by assigning an inventory item to it.
///
/// The column_name should be one of:
/// - "l_skin_0" through "l_skin_8"
/// - "l_clothes_0" through "l_clothes_8"
/// - "l_accessories_0" through "l_accessories_8"
/// - "e_{emotion}_0" through "e_{emotion}_8" (e.g., "e_happy_4")
pub async fn update_avatar_slot(
    conn: &mut PgConnection,
    user_id: Uuid,
    realm_id: Uuid,
    column_name: &str,
    inventory_id: Option<Uuid>,
) -> Result<(), AppError> {
    // Validate column name format to prevent SQL injection
    let valid_prefixes = [
        "l_skin_",
        "l_clothes_",
        "l_accessories_",
        "e_neutral_",
        "e_happy_",
        "e_sad_",
        "e_angry_",
        "e_surprised_",
        "e_thinking_",
        "e_laughing_",
        "e_crying_",
        "e_love_",
        "e_confused_",
        "e_sleeping_",
        "e_wink_",
    ];

    let is_valid = valid_prefixes
        .iter()
        .any(|prefix| column_name.starts_with(prefix))
        && column_name
            .chars()
            .last()
            .map(|c| c.is_ascii_digit() && c <= '8')
            .unwrap_or(false);

    if !is_valid {
        return Err(AppError::Validation(format!(
            "Invalid column name: {}",
            column_name
        )));
    }

    // Build dynamic UPDATE query
    // Note: We've validated the column name format above to prevent SQL injection
    let query = format!(
        r#"
        UPDATE auth.avatars
        SET {} = $3, updated_at = now()
        WHERE id = (
            SELECT avatar_id FROM auth.active_avatars
            WHERE user_id = $1 AND realm_id = $2
        )
        "#,
        column_name
    );

    let result = sqlx::query(&query)
        .bind(user_id)
        .bind(realm_id)
        .bind(inventory_id)
        .execute(&mut *conn)
        .await?;

    if result.rows_affected() == 0 {
        return Err(AppError::NotFound(
            "No active avatar for this user in this realm".to_string(),
        ));
    }

    Ok(())
}
