//! Common avatar query infrastructure.
//!
//! This module provides shared types and traits for server and realm avatars,
//! eliminating duplication between `server_avatars.rs` and `realm_avatars.rs`.

use std::collections::HashMap;

use sqlx::PgExecutor;
use uuid::Uuid;

use crate::models::{AvatarRenderData, EmotionState};
use chattyness_error::AppError;

// =============================================================================
// Avatar Slots - Array-based representation of avatar prop references
// =============================================================================

/// Number of positions per layer (0-8).
pub const LAYER_SIZE: usize = 9;

/// Number of emotion types.
pub const EMOTION_COUNT: usize = 12;

/// Array-based representation of avatar prop slots.
///
/// This struct consolidates the 135 individual UUID fields into arrays,
/// making the code more maintainable and enabling iteration.
#[derive(Debug, Clone, Default)]
pub struct AvatarSlots {
    /// Skin layer positions 0-8 (behind user, body/face).
    pub skin: [Option<Uuid>; LAYER_SIZE],
    /// Clothes layer positions 0-8 (with user, worn items).
    pub clothes: [Option<Uuid>; LAYER_SIZE],
    /// Accessories layer positions 0-8 (in front of user, held/attached items).
    pub accessories: [Option<Uuid>; LAYER_SIZE],
    /// Emotion layers: 12 emotions × 9 positions each.
    /// Index by EmotionState ordinal: neutral=0, happy=1, sad=2, etc.
    pub emotions: [[Option<Uuid>; LAYER_SIZE]; EMOTION_COUNT],
}

impl AvatarSlots {
    /// Get the emotion layer for a specific emotion state.
    pub fn emotion_layer(&self, emotion: EmotionState) -> &[Option<Uuid>; LAYER_SIZE] {
        &self.emotions[emotion.as_index()]
    }

    /// Collect all non-null prop UUIDs from all layers.
    pub fn collect_all_prop_ids(&self) -> Vec<Uuid> {
        let mut ids = Vec::new();

        // Content layers
        for id in self.skin.iter().flatten() {
            ids.push(*id);
        }
        for id in self.clothes.iter().flatten() {
            ids.push(*id);
        }
        for id in self.accessories.iter().flatten() {
            ids.push(*id);
        }

        // All emotion layers
        for emotion_layer in &self.emotions {
            for id in emotion_layer.iter().flatten() {
                ids.push(*id);
            }
        }

        ids
    }

    /// Collect prop UUIDs for content layers + specific emotion.
    pub fn collect_render_prop_ids(&self, emotion: EmotionState) -> Vec<Uuid> {
        let mut ids = Vec::new();

        // Content layers
        for id in self.skin.iter().flatten() {
            ids.push(*id);
        }
        for id in self.clothes.iter().flatten() {
            ids.push(*id);
        }
        for id in self.accessories.iter().flatten() {
            ids.push(*id);
        }

        // Specific emotion layer
        for id in self.emotion_layer(emotion).iter().flatten() {
            ids.push(*id);
        }

        ids
    }
}

/// Extension trait for EmotionState to get array indices.
pub trait EmotionIndex {
    fn as_index(&self) -> usize;
    fn from_index(index: usize) -> Option<Self>
    where
        Self: Sized;
}

impl EmotionIndex for EmotionState {
    fn as_index(&self) -> usize {
        match self {
            EmotionState::Neutral => 0,
            EmotionState::Happy => 1,
            EmotionState::Sad => 2,
            EmotionState::Angry => 3,
            EmotionState::Surprised => 4,
            EmotionState::Thinking => 5,
            EmotionState::Laughing => 6,
            EmotionState::Crying => 7,
            EmotionState::Love => 8,
            EmotionState::Confused => 9,
            EmotionState::Sleeping => 10,
            EmotionState::Wink => 11,
        }
    }

    fn from_index(index: usize) -> Option<Self> {
        match index {
            0 => Some(EmotionState::Neutral),
            1 => Some(EmotionState::Happy),
            2 => Some(EmotionState::Sad),
            3 => Some(EmotionState::Angry),
            4 => Some(EmotionState::Surprised),
            5 => Some(EmotionState::Thinking),
            6 => Some(EmotionState::Laughing),
            7 => Some(EmotionState::Crying),
            8 => Some(EmotionState::Love),
            9 => Some(EmotionState::Confused),
            10 => Some(EmotionState::Sleeping),
            11 => Some(EmotionState::Wink),
            _ => None,
        }
    }
}

// =============================================================================
// Shared Row Types for Database Queries
// =============================================================================

/// Row type for avatar with resolved asset paths.
/// Used by both server and realm avatar queries.
#[derive(Debug, sqlx::FromRow)]
pub struct AvatarWithPathsRow {
    pub id: Uuid,
    pub slug: Option<String>,  // Server avatars have slug, realm might not in some queries
    pub name: String,
    pub description: Option<String>,
    // Skin layer paths
    pub skin_0: Option<String>,
    pub skin_1: Option<String>,
    pub skin_2: Option<String>,
    pub skin_3: Option<String>,
    pub skin_4: Option<String>,
    pub skin_5: Option<String>,
    pub skin_6: Option<String>,
    pub skin_7: Option<String>,
    pub skin_8: Option<String>,
    // Clothes layer paths
    pub clothes_0: Option<String>,
    pub clothes_1: Option<String>,
    pub clothes_2: Option<String>,
    pub clothes_3: Option<String>,
    pub clothes_4: Option<String>,
    pub clothes_5: Option<String>,
    pub clothes_6: Option<String>,
    pub clothes_7: Option<String>,
    pub clothes_8: Option<String>,
    // Accessories layer paths
    pub accessories_0: Option<String>,
    pub accessories_1: Option<String>,
    pub accessories_2: Option<String>,
    pub accessories_3: Option<String>,
    pub accessories_4: Option<String>,
    pub accessories_5: Option<String>,
    pub accessories_6: Option<String>,
    pub accessories_7: Option<String>,
    pub accessories_8: Option<String>,
    // Happy emotion layer paths (e1 - for store display)
    pub emotion_0: Option<String>,
    pub emotion_1: Option<String>,
    pub emotion_2: Option<String>,
    pub emotion_3: Option<String>,
    pub emotion_4: Option<String>,
    pub emotion_5: Option<String>,
    pub emotion_6: Option<String>,
    pub emotion_7: Option<String>,
    pub emotion_8: Option<String>,
}

impl AvatarWithPathsRow {
    /// Extract skin layer as array.
    pub fn skin_layer(&self) -> [Option<String>; LAYER_SIZE] {
        [
            self.skin_0.clone(),
            self.skin_1.clone(),
            self.skin_2.clone(),
            self.skin_3.clone(),
            self.skin_4.clone(),
            self.skin_5.clone(),
            self.skin_6.clone(),
            self.skin_7.clone(),
            self.skin_8.clone(),
        ]
    }

    /// Extract clothes layer as array.
    pub fn clothes_layer(&self) -> [Option<String>; LAYER_SIZE] {
        [
            self.clothes_0.clone(),
            self.clothes_1.clone(),
            self.clothes_2.clone(),
            self.clothes_3.clone(),
            self.clothes_4.clone(),
            self.clothes_5.clone(),
            self.clothes_6.clone(),
            self.clothes_7.clone(),
            self.clothes_8.clone(),
        ]
    }

    /// Extract accessories layer as array.
    pub fn accessories_layer(&self) -> [Option<String>; LAYER_SIZE] {
        [
            self.accessories_0.clone(),
            self.accessories_1.clone(),
            self.accessories_2.clone(),
            self.accessories_3.clone(),
            self.accessories_4.clone(),
            self.accessories_5.clone(),
            self.accessories_6.clone(),
            self.accessories_7.clone(),
            self.accessories_8.clone(),
        ]
    }

    /// Extract emotion layer as array.
    pub fn emotion_layer(&self) -> [Option<String>; LAYER_SIZE] {
        [
            self.emotion_0.clone(),
            self.emotion_1.clone(),
            self.emotion_2.clone(),
            self.emotion_3.clone(),
            self.emotion_4.clone(),
            self.emotion_5.clone(),
            self.emotion_6.clone(),
            self.emotion_7.clone(),
            self.emotion_8.clone(),
        ]
    }
}

/// Row type for prop asset lookup.
#[derive(Debug, sqlx::FromRow)]
pub struct PropAssetRow {
    pub id: Uuid,
    pub asset_path: String,
}

// =============================================================================
// Generic Query Helpers
// =============================================================================

/// Build a prop UUID to asset path lookup map.
pub async fn build_prop_map<'e>(
    executor: impl PgExecutor<'e>,
    prop_ids: &[Uuid],
    props_table: &str,
) -> Result<HashMap<Uuid, String>, AppError> {
    if prop_ids.is_empty() {
        return Ok(HashMap::new());
    }

    let query = format!(
        r#"
        SELECT id, asset_path
        FROM {}
        WHERE id = ANY($1)
        "#,
        props_table
    );

    let rows = sqlx::query_as::<_, PropAssetRow>(&query)
        .bind(prop_ids)
        .fetch_all(executor)
        .await?;

    Ok(rows.into_iter().map(|r| (r.id, r.asset_path)).collect())
}

/// Resolve avatar slots to render data using a prop map.
pub fn resolve_slots_to_render_data(
    avatar_id: Uuid,
    slots: &AvatarSlots,
    current_emotion: EmotionState,
    prop_map: &HashMap<Uuid, String>,
) -> AvatarRenderData {
    let get_path = |id: Option<Uuid>| -> Option<String> {
        id.and_then(|id| prop_map.get(&id).cloned())
    };

    let emotion_layer = slots.emotion_layer(current_emotion);

    AvatarRenderData {
        avatar_id,
        current_emotion,
        skin_layer: [
            get_path(slots.skin[0]),
            get_path(slots.skin[1]),
            get_path(slots.skin[2]),
            get_path(slots.skin[3]),
            get_path(slots.skin[4]),
            get_path(slots.skin[5]),
            get_path(slots.skin[6]),
            get_path(slots.skin[7]),
            get_path(slots.skin[8]),
        ],
        clothes_layer: [
            get_path(slots.clothes[0]),
            get_path(slots.clothes[1]),
            get_path(slots.clothes[2]),
            get_path(slots.clothes[3]),
            get_path(slots.clothes[4]),
            get_path(slots.clothes[5]),
            get_path(slots.clothes[6]),
            get_path(slots.clothes[7]),
            get_path(slots.clothes[8]),
        ],
        accessories_layer: [
            get_path(slots.accessories[0]),
            get_path(slots.accessories[1]),
            get_path(slots.accessories[2]),
            get_path(slots.accessories[3]),
            get_path(slots.accessories[4]),
            get_path(slots.accessories[5]),
            get_path(slots.accessories[6]),
            get_path(slots.accessories[7]),
            get_path(slots.accessories[8]),
        ],
        emotion_layer: [
            get_path(emotion_layer[0]),
            get_path(emotion_layer[1]),
            get_path(emotion_layer[2]),
            get_path(emotion_layer[3]),
            get_path(emotion_layer[4]),
            get_path(emotion_layer[5]),
            get_path(emotion_layer[6]),
            get_path(emotion_layer[7]),
            get_path(emotion_layer[8]),
        ],
    }
}

// =============================================================================
// SQL Generation Helpers
// =============================================================================

/// Generate the SELECT clause for avatar paths query.
/// Returns the column selections for joining props to get asset paths.
pub fn avatar_paths_select_clause() -> &'static str {
    r#"
            a.id,
            a.name,
            a.description,
            -- Skin layer
            p_skin_0.asset_path AS skin_0,
            p_skin_1.asset_path AS skin_1,
            p_skin_2.asset_path AS skin_2,
            p_skin_3.asset_path AS skin_3,
            p_skin_4.asset_path AS skin_4,
            p_skin_5.asset_path AS skin_5,
            p_skin_6.asset_path AS skin_6,
            p_skin_7.asset_path AS skin_7,
            p_skin_8.asset_path AS skin_8,
            -- Clothes layer
            p_clothes_0.asset_path AS clothes_0,
            p_clothes_1.asset_path AS clothes_1,
            p_clothes_2.asset_path AS clothes_2,
            p_clothes_3.asset_path AS clothes_3,
            p_clothes_4.asset_path AS clothes_4,
            p_clothes_5.asset_path AS clothes_5,
            p_clothes_6.asset_path AS clothes_6,
            p_clothes_7.asset_path AS clothes_7,
            p_clothes_8.asset_path AS clothes_8,
            -- Accessories layer
            p_acc_0.asset_path AS accessories_0,
            p_acc_1.asset_path AS accessories_1,
            p_acc_2.asset_path AS accessories_2,
            p_acc_3.asset_path AS accessories_3,
            p_acc_4.asset_path AS accessories_4,
            p_acc_5.asset_path AS accessories_5,
            p_acc_6.asset_path AS accessories_6,
            p_acc_7.asset_path AS accessories_7,
            p_acc_8.asset_path AS accessories_8,
            -- Happy emotion layer (e1 - more inviting for store display)
            p_emo_0.asset_path AS emotion_0,
            p_emo_1.asset_path AS emotion_1,
            p_emo_2.asset_path AS emotion_2,
            p_emo_3.asset_path AS emotion_3,
            p_emo_4.asset_path AS emotion_4,
            p_emo_5.asset_path AS emotion_5,
            p_emo_6.asset_path AS emotion_6,
            p_emo_7.asset_path AS emotion_7,
            p_emo_8.asset_path AS emotion_8
    "#
}

/// Generate the JOIN clause for avatar paths query.
/// `props_table` should be "server.props" or "realm.props".
pub fn avatar_paths_join_clause(props_table: &str) -> String {
    format!(
        r#"
        -- Skin layer joins
        LEFT JOIN {props} p_skin_0 ON a.l_skin_0 = p_skin_0.id
        LEFT JOIN {props} p_skin_1 ON a.l_skin_1 = p_skin_1.id
        LEFT JOIN {props} p_skin_2 ON a.l_skin_2 = p_skin_2.id
        LEFT JOIN {props} p_skin_3 ON a.l_skin_3 = p_skin_3.id
        LEFT JOIN {props} p_skin_4 ON a.l_skin_4 = p_skin_4.id
        LEFT JOIN {props} p_skin_5 ON a.l_skin_5 = p_skin_5.id
        LEFT JOIN {props} p_skin_6 ON a.l_skin_6 = p_skin_6.id
        LEFT JOIN {props} p_skin_7 ON a.l_skin_7 = p_skin_7.id
        LEFT JOIN {props} p_skin_8 ON a.l_skin_8 = p_skin_8.id
        -- Clothes layer joins
        LEFT JOIN {props} p_clothes_0 ON a.l_clothes_0 = p_clothes_0.id
        LEFT JOIN {props} p_clothes_1 ON a.l_clothes_1 = p_clothes_1.id
        LEFT JOIN {props} p_clothes_2 ON a.l_clothes_2 = p_clothes_2.id
        LEFT JOIN {props} p_clothes_3 ON a.l_clothes_3 = p_clothes_3.id
        LEFT JOIN {props} p_clothes_4 ON a.l_clothes_4 = p_clothes_4.id
        LEFT JOIN {props} p_clothes_5 ON a.l_clothes_5 = p_clothes_5.id
        LEFT JOIN {props} p_clothes_6 ON a.l_clothes_6 = p_clothes_6.id
        LEFT JOIN {props} p_clothes_7 ON a.l_clothes_7 = p_clothes_7.id
        LEFT JOIN {props} p_clothes_8 ON a.l_clothes_8 = p_clothes_8.id
        -- Accessories layer joins
        LEFT JOIN {props} p_acc_0 ON a.l_accessories_0 = p_acc_0.id
        LEFT JOIN {props} p_acc_1 ON a.l_accessories_1 = p_acc_1.id
        LEFT JOIN {props} p_acc_2 ON a.l_accessories_2 = p_acc_2.id
        LEFT JOIN {props} p_acc_3 ON a.l_accessories_3 = p_acc_3.id
        LEFT JOIN {props} p_acc_4 ON a.l_accessories_4 = p_acc_4.id
        LEFT JOIN {props} p_acc_5 ON a.l_accessories_5 = p_acc_5.id
        LEFT JOIN {props} p_acc_6 ON a.l_accessories_6 = p_acc_6.id
        LEFT JOIN {props} p_acc_7 ON a.l_accessories_7 = p_acc_7.id
        LEFT JOIN {props} p_acc_8 ON a.l_accessories_8 = p_acc_8.id
        -- Happy emotion layer joins (e1 - more inviting for store display)
        LEFT JOIN {props} p_emo_0 ON a.e_happy_0 = p_emo_0.id
        LEFT JOIN {props} p_emo_1 ON a.e_happy_1 = p_emo_1.id
        LEFT JOIN {props} p_emo_2 ON a.e_happy_2 = p_emo_2.id
        LEFT JOIN {props} p_emo_3 ON a.e_happy_3 = p_emo_3.id
        LEFT JOIN {props} p_emo_4 ON a.e_happy_4 = p_emo_4.id
        LEFT JOIN {props} p_emo_5 ON a.e_happy_5 = p_emo_5.id
        LEFT JOIN {props} p_emo_6 ON a.e_happy_6 = p_emo_6.id
        LEFT JOIN {props} p_emo_7 ON a.e_happy_7 = p_emo_7.id
        LEFT JOIN {props} p_emo_8 ON a.e_happy_8 = p_emo_8.id
        "#,
        props = props_table
    )
}

// =============================================================================
// Macros for Avatar Slot Extraction
// =============================================================================

/// Extract AvatarSlots from an avatar struct with the standard field naming.
/// Works with both ServerAvatar and RealmAvatar.
#[macro_export]
macro_rules! extract_avatar_slots {
    ($avatar:expr) => {{
        use $crate::queries::avatar_common::AvatarSlots;

        AvatarSlots {
            skin: [
                $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,
            ],
            clothes: [
                $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,
            ],
            accessories: [
                $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,
            ],
            emotions: [
                // Neutral (e0)
                [
                    $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,
                ],
                // Happy (e1)
                [
                    $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,
                ],
                // Sad (e2)
                [
                    $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,
                ],
                // Angry (e3)
                [
                    $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,
                ],
                // Surprised (e4)
                [
                    $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,
                ],
                // Thinking (e5)
                [
                    $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,
                ],
                // Laughing (e6)
                [
                    $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,
                ],
                // Crying (e7)
                [
                    $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,
                ],
                // Love (e8)
                [
                    $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,
                ],
                // Confused (e9)
                [
                    $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,
                ],
                // Sleeping (e10)
                [
                    $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,
                ],
                // Wink (e11)
                [
                    $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,
                ],
            ],
        }
    }};
}

pub use extract_avatar_slots;
