//! Social graph types for AiFastDb
//!
//! Defines input/output types for social graph operations.

use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

use crate::types::graph::Entity;

// ============================================================================
// Person Types
// ============================================================================

/// Input for creating a person
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersonInput {
    /// Optional ID (will be auto-generated if not provided)
    pub id: Option<String>,
    /// Person's name
    pub name: String,
    /// Avatar URL
    #[serde(default)]
    pub avatar: Option<String>,
    /// Description
    #[serde(default)]
    pub description: Option<String>,
    /// Phone number
    #[serde(default)]
    pub phone: Option<String>,
    /// Email address
    #[serde(default)]
    pub email: Option<String>,
    /// WeChat ID
    #[serde(default)]
    pub wechat: Option<String>,
    /// Additional properties
    #[serde(default)]
    pub properties: BTreeMap<String, serde_json::Value>,
}

/// Filter for querying persons
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PersonFilter {
    /// Filter by name (partial match)
    pub name: Option<String>,
    /// Filter by tag IDs
    pub tags: Option<Vec<String>>,
    /// Filter by attributes
    pub attributes: Option<BTreeMap<String, String>>,
    /// Maximum number of results
    pub limit: Option<usize>,
}

/// Configuration for friend relationship
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FriendConfig {
    /// Relation type (friend, knows, colleague, etc.)
    #[serde(default = "default_friend_type")]
    pub relation_type: String,
    /// Relationship weight/strength (0.0 - 1.0)
    #[serde(default = "default_weight")]
    pub weight: f64,
    /// Whether the relationship is bidirectional
    #[serde(default = "default_bidirectional")]
    pub bidirectional: bool,
    /// When they became friends
    #[serde(default)]
    pub since: Option<String>,
    /// Who introduced them
    #[serde(default)]
    pub via: Option<String>,
    /// Additional metadata
    #[serde(default)]
    pub metadata: BTreeMap<String, serde_json::Value>,
}

fn default_friend_type() -> String {
    "friend".to_string()
}

fn default_weight() -> f64 {
    1.0
}

fn default_bidirectional() -> bool {
    true
}

impl Default for FriendConfig {
    fn default() -> Self {
        Self {
            relation_type: default_friend_type(),
            weight: default_weight(),
            bidirectional: default_bidirectional(),
            since: None,
            via: None,
            metadata: BTreeMap::new(),
        }
    }
}

// ============================================================================
// Tag Types
// ============================================================================

/// Input for creating a tag
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TagInput {
    /// Optional ID
    pub id: Option<String>,
    /// Tag name
    pub name: String,
    /// Tag type (tag, company, organization, skill, etc.)
    /// Accepts both snake_case (tag_type) and camelCase (tagType) from JSON
    #[serde(default = "default_tag_type", alias = "tagType")]
    pub tag_type: String,
    /// Description
    #[serde(default)]
    pub description: Option<String>,
    /// Display color
    #[serde(default)]
    pub color: Option<String>,
    /// Parent tag IDs
    /// Accepts both snake_case (parent_tags) and camelCase (parentTags) from JSON
    #[serde(default, alias = "parentTags")]
    pub parent_tags: Vec<String>,
    /// Additional properties
    #[serde(default)]
    pub properties: BTreeMap<String, serde_json::Value>,
}

fn default_tag_type() -> String {
    "tag".to_string()
}

// ============================================================================
// Item Types
// ============================================================================

/// Input for creating an item
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ItemInput {
    /// Optional ID
    pub id: Option<String>,
    /// Item name
    pub name: String,
    /// Item category (vehicle, phone, computer, etc.)
    pub category: String,
    /// Brand name
    #[serde(default)]
    pub brand: Option<String>,
    /// Model name
    #[serde(default)]
    pub model: Option<String>,
    /// Additional properties
    #[serde(default)]
    pub properties: BTreeMap<String, serde_json::Value>,
}

/// Result of shared item discovery
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SharedItemResult {
    /// The shared item
    pub item: Entity,
    /// People who own this item
    pub owners: Vec<Entity>,
    /// Number of owners
    pub owner_count: usize,
}

/// Result of item-based person matching
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ItemMatchResult {
    /// The matched person
    pub person: Entity,
    /// Shared items with this person
    pub shared_items: Vec<Entity>,
    /// Match score (0.0 - 1.0)
    pub match_score: f64,
    /// Match details
    pub match_details: Vec<ItemMatchDetail>,
}

/// Detail of an item match
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ItemMatchDetail {
    /// The item
    pub item: Entity,
    /// Relation type (owns, wants, used_to_own)
    pub relation_type: String,
}

// ============================================================================
// Attribute Types
// ============================================================================

/// Input for creating an attribute
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttributeInput {
    /// Optional ID
    pub id: Option<String>,
    /// Attribute category (zodiac, school, city, hobby, etc.)
    pub category: String,
    /// Attribute value
    pub value: String,
    /// Additional properties
    #[serde(default)]
    pub properties: BTreeMap<String, serde_json::Value>,
}

/// Configuration for attribute matching
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttributeMatchConfig {
    /// Weight for each category
    #[serde(default)]
    pub category_weights: BTreeMap<String, f64>,
    /// Minimum number of matching attributes
    #[serde(default = "default_min_match")]
    pub min_match_count: usize,
    /// Only include these categories
    #[serde(default)]
    pub include_categories: Option<Vec<String>>,
    /// Exclude these categories
    #[serde(default)]
    pub exclude_categories: Option<Vec<String>>,
}

fn default_min_match() -> usize {
    1
}

impl Default for AttributeMatchConfig {
    fn default() -> Self {
        let mut weights = BTreeMap::new();
        weights.insert("school".to_string(), 0.9);
        weights.insert("city".to_string(), 0.7);
        weights.insert("industry".to_string(), 0.8);
        weights.insert("hobby".to_string(), 0.6);
        weights.insert("zodiac".to_string(), 0.3);
        weights.insert("mbti".to_string(), 0.4);
        weights.insert("age_group".to_string(), 0.5);
        
        Self {
            category_weights: weights,
            min_match_count: 1,
            include_categories: None,
            exclude_categories: None,
        }
    }
}

/// Result of shared attribute discovery
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SharedAttributeResult {
    /// The shared attribute
    pub attribute: Entity,
    /// Attribute category
    pub category: String,
    /// People with this attribute
    pub persons: Vec<Entity>,
    /// Number of persons
    pub person_count: usize,
}

/// Detail of an attribute match
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SharedAttributeDetail {
    /// The attribute
    pub attribute: Entity,
    /// Attribute category
    pub category: String,
    /// Weight for this match
    pub weight: f64,
}

/// Result of attribute-based person matching
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttributeMatchResult {
    /// The matched person
    pub person: Entity,
    /// Shared attributes with this person
    pub shared_attributes: Vec<SharedAttributeDetail>,
    /// Match score (0.0 - 1.0)
    pub match_score: f64,
    /// Optional compatibility analysis
    pub compatibility_analysis: Option<String>,
}

/// Result of compatibility analysis
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompatibilityResult {
    /// Overall compatibility score (0.0 - 1.0)
    pub overall_score: f64,
    /// Shared attributes
    pub shared_attributes: Vec<SharedAttributeDetail>,
    /// Complementary attributes
    pub complementary_attributes: Vec<String>,
    /// Analysis text
    pub analysis: String,
}

// ============================================================================
// Path Finding Types
// ============================================================================

/// Result of path finding
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PathResult {
    /// Complete path of entities
    pub path: Vec<Entity>,
    /// Number of hops
    pub hops: usize,
    /// Intermediaries (excluding start and end)
    pub intermediaries: Vec<Entity>,
    /// Chain of relations
    pub relation_chain: Vec<crate::types::graph::Relation>,
}

/// Result of finding all paths
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AllPathsResult {
    /// All found paths
    pub paths: Vec<PathResult>,
    /// Shortest number of hops
    pub shortest_hops: usize,
}

/// Options for path finding
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PathOptions {
    /// Maximum number of hops
    pub max_hops: Option<usize>,
    /// Only follow these relation types
    pub relation_types: Option<Vec<String>>,
    /// Use bidirectional search
    pub bidirectional_search: bool,
}

// ============================================================================
// Network Analysis Types
// ============================================================================

/// N-degree contacts result
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct DegreeContacts {
    /// Direct friends (1st degree)
    pub degree1: Vec<Entity>,
    /// Friends of friends (2nd degree)
    pub degree2: Vec<Entity>,
    /// 3rd degree connections
    pub degree3: Vec<Entity>,
}

/// Relationship strength between two people
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelationshipStrength {
    /// Direct relationship strength (0 if no direct connection)
    pub direct_strength: f64,
    /// Number of mutual friends
    pub mutual_friends_count: usize,
    /// Number of mutual tags
    pub mutual_tags_count: usize,
    /// Number of mutual items
    pub mutual_items_count: usize,
    /// Number of mutual attributes
    pub mutual_attributes_count: usize,
    /// Shortest path length
    pub shortest_path_length: usize,
    /// Overall score (0.0 - 1.0)
    pub overall_score: f64,
}

impl Default for RelationshipStrength {
    fn default() -> Self {
        Self {
            direct_strength: 0.0,
            mutual_friends_count: 0,
            mutual_tags_count: 0,
            mutual_items_count: 0,
            mutual_attributes_count: 0,
            shortest_path_length: usize::MAX,
            overall_score: 0.0,
        }
    }
}

/// Reason for connection suggestion
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum SuggestionReason {
    /// Has N mutual friends
    MutualFriend { count: usize },
    /// Belongs to same tag/organization
    MutualTag { tag_name: String },
    /// Friend of a friend
    SecondDegree,
    /// Similar profile
    SimilarProfile,
    /// Shares an item
    SharedItem { item_name: String },
    /// Same brand
    SameBrand { brand: String },
    /// Same school (alumni)
    SameSchool { school: String },
    /// Same city
    SameCity { city: String },
    /// Same zodiac
    SameZodiac { zodiac: String },
    /// Same hobby
    SameHobby { hobby: String },
    /// Same industry
    SameIndustry { industry: String },
    /// Same MBTI
    SameMBTI { mbti: String },
    /// High compatibility score
    HighCompatibility { score: f64 },
}

/// Connection suggestion
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionSuggestion {
    /// Suggested person
    pub person: Entity,
    /// Suggestion score (0.0 - 1.0)
    pub score: f64,
    /// Reasons for suggestion
    pub reasons: Vec<SuggestionReason>,
    /// Who can introduce (optional)
    pub via: Option<Entity>,
}

/// Configuration for connection suggestions
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SuggestionConfig {
    /// Weight for mutual friends
    #[serde(default = "default_friend_weight")]
    pub friend_weight: f64,
    /// Weight for mutual tags
    #[serde(default = "default_tag_weight")]
    pub tag_weight: f64,
    /// Weight for shared items
    #[serde(default = "default_item_weight")]
    pub item_weight: f64,
    /// Weight for shared attributes
    #[serde(default = "default_attribute_weight")]
    pub attribute_weight: f64,
    /// Weight for path distance
    #[serde(default = "default_path_weight")]
    pub path_weight: f64,
    /// Attribute matching config
    #[serde(default)]
    pub attribute_config: AttributeMatchConfig,
    /// Exclude existing friends
    #[serde(default = "default_exclude_friends")]
    pub exclude_existing_friends: bool,
    /// Minimum score threshold
    #[serde(default)]
    pub min_score: f64,
    /// Maximum results
    #[serde(default = "default_max_results")]
    pub max_results: usize,
}

fn default_friend_weight() -> f64 { 0.3 }
fn default_tag_weight() -> f64 { 0.2 }
fn default_item_weight() -> f64 { 0.15 }
fn default_attribute_weight() -> f64 { 0.25 }
fn default_path_weight() -> f64 { 0.1 }
fn default_exclude_friends() -> bool { true }
fn default_max_results() -> usize { 20 }

impl Default for SuggestionConfig {
    fn default() -> Self {
        Self {
            friend_weight: default_friend_weight(),
            tag_weight: default_tag_weight(),
            item_weight: default_item_weight(),
            attribute_weight: default_attribute_weight(),
            path_weight: default_path_weight(),
            attribute_config: AttributeMatchConfig::default(),
            exclude_existing_friends: default_exclude_friends(),
            min_score: 0.0,
            max_results: default_max_results(),
        }
    }
}

/// Network statistics for a person
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkStats {
    /// Total direct friends
    pub total_friends: usize,
    /// Total tags
    pub total_tags: usize,
    /// Total items
    pub total_items: usize,
    /// Total attributes
    pub total_attributes: usize,
    /// People reachable within 2 hops
    pub network_reach_2hop: usize,
    /// People reachable within 3 hops
    pub network_reach_3hop: usize,
    /// Average path length to others
    pub avg_path_length: f64,
    /// Clustering coefficient
    pub clustering_coefficient: f64,
}

impl Default for NetworkStats {
    fn default() -> Self {
        Self {
            total_friends: 0,
            total_tags: 0,
            total_items: 0,
            total_attributes: 0,
            network_reach_2hop: 0,
            network_reach_3hop: 0,
            avg_path_length: 0.0,
            clustering_coefficient: 0.0,
        }
    }
}

// ============================================================================
// Graph Export Types (for visualization)
// ============================================================================

/// Filter for listing tags
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TagFilter {
    /// Filter by tag type (tag/company/organization/skill/interest)
    #[serde(default)]
    pub tag_type: Option<String>,
    /// Filter by name (partial match, case-insensitive)
    #[serde(default)]
    pub name: Option<String>,
    /// Maximum number of results
    #[serde(default)]
    pub limit: Option<usize>,
}

/// Filter for listing relations
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RelationFilter {
    /// Filter by relation type (friend/colleague/tagged/work_for/...)
    #[serde(default)]
    pub relation_type: Option<String>,
    /// Filter by source entity ID
    #[serde(default)]
    pub source_id: Option<String>,
    /// Filter by target entity ID
    #[serde(default)]
    pub target_id: Option<String>,
    /// Maximum number of results
    #[serde(default)]
    pub limit: Option<usize>,
}

/// Node group type for visualization
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum NodeGroup {
    /// Person node
    Person,
    /// Company node
    Company,
    /// Tag node (including organization, skill, interest, etc.)
    Tag,
}

impl std::fmt::Display for NodeGroup {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            NodeGroup::Person => write!(f, "person"),
            NodeGroup::Company => write!(f, "company"),
            NodeGroup::Tag => write!(f, "tag"),
        }
    }
}

/// Node for graph visualization
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphNode {
    /// Unique node ID
    pub id: String,
    /// Display label
    pub label: String,
    /// Node group (determines color/shape in visualization)
    pub group: NodeGroup,
    /// Tooltip content
    pub title: String,
    /// Node degree (connection count), used by visualization layer
    #[serde(skip_serializing_if = "Option::is_none")]
    pub degree: Option<usize>,
    /// Additional node data
    #[serde(default)]
    pub data: BTreeMap<String, serde_json::Value>,
}

impl GraphNode {
    /// Create a new graph node
    pub fn new(id: impl Into<String>, label: impl Into<String>, group: NodeGroup) -> Self {
        let label = label.into();
        Self {
            id: id.into(),
            label: label.clone(),
            group,
            title: label,
            degree: None,
            data: BTreeMap::new(),
        }
    }
    
    /// Set title
    pub fn with_title(mut self, title: impl Into<String>) -> Self {
        self.title = title.into();
        self
    }
    
    /// Add data property
    pub fn with_data(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
        self.data.insert(key.into(), value);
        self
    }
    
    /// Set all data from Entity properties
    pub fn with_data_from(mut self, properties: BTreeMap<String, serde_json::Value>) -> Self {
        self.data = properties;
        self
    }
}

/// Edge for graph visualization
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphEdge {
    /// Unique edge ID
    pub id: String,
    /// Source node ID
    pub from: String,
    /// Target node ID
    pub to: String,
    /// Display label on edge
    pub label: String,
    /// Relation type
    pub relation_type: String,
    /// Edge weight (for layout/styling)
    #[serde(default = "default_edge_weight")]
    pub weight: f64,
    /// Additional edge data
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<BTreeMap<String, serde_json::Value>>,
}

fn default_edge_weight() -> f64 {
    1.0
}

impl GraphEdge {
    /// Create a new graph edge
    pub fn new(
        id: impl Into<String>,
        from: impl Into<String>,
        to: impl Into<String>,
        relation_type: impl Into<String>,
    ) -> Self {
        let rel_type = relation_type.into();
        Self {
            id: id.into(),
            from: from.into(),
            to: to.into(),
            label: Self::relation_type_label(&rel_type),
            relation_type: rel_type,
            weight: 1.0,
            data: None,
        }
    }
    
    /// Set label
    pub fn with_label(mut self, label: impl Into<String>) -> Self {
        self.label = label.into();
        self
    }
    
    /// Set weight
    pub fn with_weight(mut self, weight: f64) -> Self {
        self.weight = weight;
        self
    }
    
    /// Set data
    pub fn with_data(mut self, data: BTreeMap<String, serde_json::Value>) -> Self {
        self.data = Some(data);
        self
    }
    
    /// Get human-readable label for relation type
    pub fn relation_type_label(rel_type: &str) -> String {
        match rel_type {
            "friend" => "好友",
            "knows" => "认识",
            "colleague" => "同事",
            "classmate" => "同学",
            "family" => "家人",
            "mentor" => "导师",
            "belongs_to" | "tagged" => "标签",
            "work_for" => "就职于",
            "owns" => "拥有",
            "member_of" => "成员",
            "parent_of" => "父级",
            _ => rel_type,
        }.to_string()
    }
}

/// Graph statistics
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GraphStats {
    /// Number of person nodes
    pub persons: usize,
    /// Number of company nodes
    pub companies: usize,
    /// Number of tag nodes
    pub tags: usize,
    /// Total number of relations (edges)
    pub relations: usize,
}

impl GraphStats {
    /// Create new stats
    pub fn new(persons: usize, companies: usize, tags: usize, relations: usize) -> Self {
        Self { persons, companies, tags, relations }
    }
    
    /// Total number of nodes
    pub fn total_nodes(&self) -> usize {
        self.persons + self.companies + self.tags
    }
}

/// Exported graph data for visualization
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportedGraph {
    /// All nodes in the graph
    pub nodes: Vec<GraphNode>,
    /// All edges in the graph
    pub edges: Vec<GraphEdge>,
    /// Graph statistics
    pub stats: GraphStats,
}

impl ExportedGraph {
    /// Create a new empty graph
    pub fn new() -> Self {
        Self {
            nodes: Vec::new(),
            edges: Vec::new(),
            stats: GraphStats::default(),
        }
    }
    
    /// Add a node
    pub fn add_node(&mut self, node: GraphNode) {
        self.nodes.push(node);
    }
    
    /// Add an edge
    pub fn add_edge(&mut self, edge: GraphEdge) {
        self.edges.push(edge);
    }
    
    /// Update stats based on current nodes and edges
    pub fn update_stats(&mut self) {
        let mut persons = 0;
        let mut companies = 0;
        let mut tags = 0;
        
        for node in &self.nodes {
            match node.group {
                NodeGroup::Person => persons += 1,
                NodeGroup::Company => companies += 1,
                NodeGroup::Tag => tags += 1,
            }
        }
        
        self.stats = GraphStats {
            persons,
            companies,
            tags,
            relations: self.edges.len(),
        };
    }
}

impl Default for ExportedGraph {
    fn default() -> Self {
        Self::new()
    }
}

/// Paginated result wrapper (Phase-97)
///
/// Generic container for paginated query results.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaginatedResult<T> {
    /// The items in the current page
    pub items: Vec<T>,
    /// Total number of items matching the query
    pub total: usize,
    /// Current offset
    pub offset: usize,
    /// Page size limit used
    pub limit: usize,
    /// Whether there are more items after this page
    pub has_more: bool,
}

impl<T> PaginatedResult<T> {
    /// Create a new paginated result
    pub fn new(items: Vec<T>, total: usize, offset: usize, limit: usize) -> Self {
        Self {
            has_more: offset + limit < total,
            items,
            total,
            offset,
            limit,
        }
    }
    
    /// Create an empty result
    pub fn empty(offset: usize, limit: usize) -> Self {
        Self {
            items: Vec::new(),
            total: 0,
            offset,
            limit,
            has_more: false,
        }
    }
}

/// Options for paginated graph export (Phase-97)
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ExportGraphPaginatedOptions {
    /// Offset (number of nodes to skip)
    #[serde(default)]
    pub offset: usize,
    /// Maximum number of nodes to return per page
    #[serde(default = "default_page_limit")]
    pub limit: usize,
    /// Entity types to include (None = all types)
    #[serde(default)]
    pub entity_types: Option<Vec<String>>,
    /// Relation types to include (None = all relation types)
    #[serde(default)]
    pub relation_types: Option<Vec<String>>,
    /// Include tag nodes (default: true)
    #[serde(default)]
    pub include_tags: Option<bool>,
    /// Include company nodes (default: true)
    #[serde(default)]
    pub include_companies: Option<bool>,
    /// Include edge metadata (default: true)
    #[serde(default)]
    pub include_edge_meta: Option<bool>,
    /// Include node degree (default: true)
    #[serde(default)]
    pub include_node_degree: Option<bool>,
}

fn default_page_limit() -> usize {
    5000
}

impl ExportGraphPaginatedOptions {
    /// Get limit with default
    pub fn get_limit(&self) -> usize {
        if self.limit == 0 { 5000 } else { self.limit }
    }
}

/// Paginated graph export result (Phase-97)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaginatedExportedGraph {
    /// Nodes in the current page
    pub nodes: Vec<GraphNode>,
    /// Edges where both endpoints are in the current page
    pub edges: Vec<GraphEdge>,
    /// Graph statistics
    pub stats: GraphStats,
    /// Total number of nodes (across all pages)
    pub total_nodes: usize,
    /// Total number of edges in full graph
    pub total_edges: usize,
    /// Current offset
    pub offset: usize,
    /// Page size used
    pub limit: usize,
    /// Whether there are more nodes after this page
    pub has_more: bool,
}

/// Compact binary graph export (Phase-98)
///
/// Binary format designed for zero-copy TypedArray consumption:
///
/// ```text
/// Header (16 bytes):
///   [0..4]  magic: u32 = 0x41494647 ("AIFG")
///   [4..8]  version: u32 = 1
///   [8..12] node_count: u32
///   [12..16] edge_count: u32
///
/// String Table (variable):
///   For each node (node_count times):
///     [2 bytes] id_len: u16
///     [id_len bytes] id: UTF-8
///     [1 byte] group: u8 (0=Person, 1=Company, 2=Tag)
///     [2 bytes] label_len: u16
///     [label_len bytes] label: UTF-8
///     [4 bytes] degree: u32
///
/// Edge Table (12 bytes per edge):
///   For each edge (edge_count times):
///     [4 bytes] from_index: u32
///     [4 bytes] to_index: u32
///     [4 bytes] weight_x1000: u32  (weight * 1000 as integer)
/// ```
#[derive(Debug)]
pub struct CompactGraphExport {
    /// The raw binary buffer
    pub buffer: Vec<u8>,
    /// Number of nodes encoded
    pub node_count: u32,
    /// Number of edges encoded
    pub edge_count: u32,
}

/// Group summary for server-side aggregation (Phase-98)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntityGroupSummary {
    /// Entity type name
    pub entity_type: String,
    /// Count of entities in this group
    pub count: usize,
    /// Sample entity IDs (up to 5)
    pub sample_ids: Vec<String>,
}

/// Full aggregation result (Phase-98)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntityGroupAggregation {
    /// Group summaries keyed by entity type
    pub groups: Vec<EntityGroupSummary>,
    /// Total entity count across all groups
    pub total_entities: usize,
    /// Total relation count
    pub total_relations: usize,
}

/// Options for exporting graph
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ExportGraphOptions {
    /// Maximum number of nodes to return (default: 500)
    #[serde(default)]
    pub max_nodes: Option<usize>,
    /// Maximum number of edges to return
    #[serde(default)]
    pub max_edges: Option<usize>,
    /// Include tag nodes (default: true)
    #[serde(default)]
    pub include_tags: Option<bool>,
    /// Include company nodes (default: true)
    #[serde(default)]
    pub include_companies: Option<bool>,
    /// Include edge metadata like weight/relationType (default: true)
    #[serde(default)]
    pub include_edge_meta: Option<bool>,
    /// Include node degree in GraphNode (default: true)
    #[serde(default)]
    pub include_node_degree: Option<bool>,
    /// Only include these relation types (e.g., ["friend", "colleague"])
    #[serde(default)]
    pub relation_types: Option<Vec<String>>,
}

impl ExportGraphOptions {
    /// Create with default values
    pub fn new() -> Self {
        Self::default()
    }
    
    /// Set max nodes
    pub fn with_max_nodes(mut self, max: usize) -> Self {
        self.max_nodes = Some(max);
        self
    }
    
    /// Set max edges
    pub fn with_max_edges(mut self, max: usize) -> Self {
        self.max_edges = Some(max);
        self
    }
    
    /// Set include tags
    pub fn with_tags(mut self, include: bool) -> Self {
        self.include_tags = Some(include);
        self
    }
    
    /// Set include companies
    pub fn with_companies(mut self, include: bool) -> Self {
        self.include_companies = Some(include);
        self
    }
    
    /// Set relation types filter
    pub fn with_relation_types(mut self, types: Vec<String>) -> Self {
        self.relation_types = Some(types);
        self
    }
    
    /// Get max_nodes with default
    pub fn get_max_nodes(&self) -> usize {
        self.max_nodes.unwrap_or(500)
    }
    
    /// Get include_tags with default
    pub fn get_include_tags(&self) -> bool {
        self.include_tags.unwrap_or(true)
    }
    
    /// Get include_companies with default
    pub fn get_include_companies(&self) -> bool {
        self.include_companies.unwrap_or(true)
    }
    
    /// Get include_edge_meta with default
    pub fn get_include_edge_meta(&self) -> bool {
        self.include_edge_meta.unwrap_or(true)
    }

    /// Get include_node_degree with default
    pub fn get_include_node_degree(&self) -> bool {
        self.include_node_degree.unwrap_or(true)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_friend_config_default() {
        let config = FriendConfig::default();
        assert_eq!(config.relation_type, "friend");
        assert_eq!(config.weight, 1.0);
        assert!(config.bidirectional);
    }

    #[test]
    fn test_attribute_match_config_default() {
        let config = AttributeMatchConfig::default();
        assert_eq!(config.category_weights.get("school"), Some(&0.9));
        assert_eq!(config.category_weights.get("zodiac"), Some(&0.3));
        assert_eq!(config.min_match_count, 1);
    }

    #[test]
    fn test_suggestion_config_default() {
        let config = SuggestionConfig::default();
        assert_eq!(config.friend_weight, 0.3);
        assert_eq!(config.attribute_weight, 0.25);
        assert!(config.exclude_existing_friends);
        assert_eq!(config.max_results, 20);
    }
    
    // ========================================================================
    // Graph Export Types Tests
    // ========================================================================
    
    #[test]
    fn test_tag_filter_default() {
        let filter = TagFilter::default();
        assert!(filter.tag_type.is_none());
        assert!(filter.name.is_none());
        assert!(filter.limit.is_none());
    }
    
    #[test]
    fn test_relation_filter_default() {
        let filter = RelationFilter::default();
        assert!(filter.relation_type.is_none());
        assert!(filter.source_id.is_none());
        assert!(filter.target_id.is_none());
        assert!(filter.limit.is_none());
    }
    
    #[test]
    fn test_node_group_serialization() {
        assert_eq!(NodeGroup::Person.to_string(), "person");
        assert_eq!(NodeGroup::Company.to_string(), "company");
        assert_eq!(NodeGroup::Tag.to_string(), "tag");
        
        // Test serde serialization
        let json = serde_json::to_string(&NodeGroup::Person).unwrap();
        assert_eq!(json, "\"person\"");
        
        let deserialized: NodeGroup = serde_json::from_str("\"company\"").unwrap();
        assert_eq!(deserialized, NodeGroup::Company);
    }
    
    #[test]
    fn test_graph_node_creation() {
        let node = GraphNode::new("id1", "Alice", NodeGroup::Person)
            .with_title("Alice's profile")
            .with_data("email", serde_json::json!("alice@example.com"));
        
        assert_eq!(node.id, "id1");
        assert_eq!(node.label, "Alice");
        assert_eq!(node.group, NodeGroup::Person);
        assert_eq!(node.title, "Alice's profile");
        assert_eq!(node.degree, None);
        assert_eq!(node.data.get("email"), Some(&serde_json::json!("alice@example.com")));
    }
    
    #[test]
    fn test_graph_edge_creation() {
        let edge = GraphEdge::new("e1", "id1", "id2", "friend")
            .with_weight(0.8);
        
        assert_eq!(edge.id, "e1");
        assert_eq!(edge.from, "id1");
        assert_eq!(edge.to, "id2");
        assert_eq!(edge.relation_type, "friend");
        assert_eq!(edge.label, "好友"); // Chinese label for "friend"
        assert_eq!(edge.weight, 0.8);
    }
    
    #[test]
    fn test_graph_edge_labels() {
        assert_eq!(GraphEdge::relation_type_label("friend"), "好友");
        assert_eq!(GraphEdge::relation_type_label("colleague"), "同事");
        assert_eq!(GraphEdge::relation_type_label("work_for"), "就职于");
        assert_eq!(GraphEdge::relation_type_label("unknown"), "unknown");
    }
    
    #[test]
    fn test_graph_stats() {
        let stats = GraphStats::new(10, 3, 5, 20);
        assert_eq!(stats.persons, 10);
        assert_eq!(stats.companies, 3);
        assert_eq!(stats.tags, 5);
        assert_eq!(stats.relations, 20);
        assert_eq!(stats.total_nodes(), 18);
    }
    
    #[test]
    fn test_exported_graph() {
        let mut graph = ExportedGraph::new();
        
        graph.add_node(GraphNode::new("p1", "Alice", NodeGroup::Person));
        graph.add_node(GraphNode::new("p2", "Bob", NodeGroup::Person));
        graph.add_node(GraphNode::new("c1", "Google", NodeGroup::Company));
        
        graph.add_edge(GraphEdge::new("e1", "p1", "p2", "friend"));
        graph.add_edge(GraphEdge::new("e2", "p1", "c1", "work_for"));
        
        graph.update_stats();
        
        assert_eq!(graph.nodes.len(), 3);
        assert_eq!(graph.edges.len(), 2);
        assert_eq!(graph.stats.persons, 2);
        assert_eq!(graph.stats.companies, 1);
        assert_eq!(graph.stats.tags, 0);
        assert_eq!(graph.stats.relations, 2);
    }
    
    #[test]
    fn test_export_graph_options() {
        let opts = ExportGraphOptions::new()
            .with_max_nodes(100)
            .with_tags(false)
            .with_companies(true)
            .with_relation_types(vec!["friend".to_string()]);
        
        assert_eq!(opts.get_max_nodes(), 100);
        assert!(!opts.get_include_tags());
        assert!(opts.get_include_companies());
        assert_eq!(opts.relation_types, Some(vec!["friend".to_string()]));
    }
    
    #[test]
    fn test_export_graph_options_defaults() {
        let opts = ExportGraphOptions::default();
        assert_eq!(opts.get_max_nodes(), 500);
        assert!(opts.get_include_tags());
        assert!(opts.get_include_companies());
        assert!(opts.get_include_edge_meta());
        assert!(opts.get_include_node_degree());
    }
}
