//! Data models for the ChainStream WebSocket Stream API
//!
//! This module contains all the data structures used by the stream API
//! for real-time data subscriptions.

use serde::{Deserialize, Serialize};

// Re-export Resolution from openapi for convenience
pub use crate::openapi::types::Resolution;

/// Token activity type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum TokenActivityType {
    Sell,
    Buy,
    AddLiquidity,
    RemoveLiquidity,
}

/// Channel type for subscriptions
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ChannelType {
    New,
    #[serde(rename = "trending")]
    Hot,
    UsStocks,
    #[serde(rename = "graduated")]
    FinalStretch,
    #[serde(rename = "completed")]
    Migrated,
}

/// Metric type for measurements
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum MetricType {
    LiquidityInUsd,
    MigratedRatio,
}

/// Ranking type for token rankings
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RankingType {
    New,
    #[serde(rename = "trending")]
    Hot,
    Stocks,
    #[serde(rename = "completed")]
    FinalStretch,
    #[serde(rename = "graduated")]
    Migrated,
}

/// DEX (Decentralized Exchange) type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Dex {
    PumpFun,
    RaydiumLaunchpad,
    MeteoraDynamicBoundingCurve,
    BonkFun,
    BoopFun,
    MoonitFun,
}

/// Token activity data
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenActivity {
    pub address: String,
    pub price_usd: String,
    pub amount: String,
    #[serde(rename = "type")]
    pub activity_type: TokenActivityType,
    pub tx_hash: String,
    pub timestamp: i64,
}

/// Token statistics with multi-timeframe data
/// Time windows: 1m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 24h, 1W, 1M
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TokenStat {
    #[serde(rename = "a")]
    pub address: String,
    #[serde(rename = "t")]
    pub timestamp: i64,

    // Current price (first positive closeInUsd across windows, or from cache)
    #[serde(rename = "p", skip_serializing_if = "Option::is_none")]
    pub price: Option<String>,

    // 1-minute data
    #[serde(rename = "b1m", skip_serializing_if = "Option::is_none")]
    pub buys_1m: Option<i64>,
    #[serde(rename = "s1m", skip_serializing_if = "Option::is_none")]
    pub sells_1m: Option<i64>,
    #[serde(rename = "be1m", skip_serializing_if = "Option::is_none")]
    pub buyers_1m: Option<i64>,
    #[serde(rename = "se1m", skip_serializing_if = "Option::is_none")]
    pub sellers_1m: Option<i64>,
    #[serde(rename = "bviu1m", skip_serializing_if = "Option::is_none")]
    pub buy_volume_in_usd_1m: Option<String>,
    #[serde(rename = "sviu1m", skip_serializing_if = "Option::is_none")]
    pub sell_volume_in_usd_1m: Option<String>,
    #[serde(rename = "p1m", skip_serializing_if = "Option::is_none")]
    pub price_1m: Option<String>,
    #[serde(rename = "oiu1m", skip_serializing_if = "Option::is_none")]
    pub open_in_usd_1m: Option<String>,
    #[serde(rename = "ciu1m", skip_serializing_if = "Option::is_none")]
    pub close_in_usd_1m: Option<String>,
    #[serde(rename = "vpc1m", skip_serializing_if = "Option::is_none")]
    pub volume_change_ratio_1m: Option<String>,
    #[serde(rename = "tr1m", skip_serializing_if = "Option::is_none")]
    pub trades_1m: Option<i64>,
    #[serde(rename = "dpc1m", skip_serializing_if = "Option::is_none")]
    pub dapp_program_count_1m: Option<i32>,
    #[serde(rename = "pc1m", skip_serializing_if = "Option::is_none")]
    pub pool_count_1m: Option<i32>,
    #[serde(rename = "liq1m", skip_serializing_if = "Option::is_none")]
    pub liquidity_in_usd_1m: Option<String>,
    #[serde(rename = "lpc1m", skip_serializing_if = "Option::is_none")]
    pub liquidity_change_ratio_1m: Option<String>,

    // 5-minute data
    #[serde(rename = "b5m", skip_serializing_if = "Option::is_none")]
    pub buys_5m: Option<i64>,
    #[serde(rename = "s5m", skip_serializing_if = "Option::is_none")]
    pub sells_5m: Option<i64>,
    #[serde(rename = "be5m", skip_serializing_if = "Option::is_none")]
    pub buyers_5m: Option<i64>,
    #[serde(rename = "se5m", skip_serializing_if = "Option::is_none")]
    pub sellers_5m: Option<i64>,
    #[serde(rename = "bviu5m", skip_serializing_if = "Option::is_none")]
    pub buy_volume_in_usd_5m: Option<String>,
    #[serde(rename = "sviu5m", skip_serializing_if = "Option::is_none")]
    pub sell_volume_in_usd_5m: Option<String>,
    #[serde(rename = "p5m", skip_serializing_if = "Option::is_none")]
    pub price_5m: Option<String>,
    #[serde(rename = "oiu5m", skip_serializing_if = "Option::is_none")]
    pub open_in_usd_5m: Option<String>,
    #[serde(rename = "ciu5m", skip_serializing_if = "Option::is_none")]
    pub close_in_usd_5m: Option<String>,
    #[serde(rename = "vpc5m", skip_serializing_if = "Option::is_none")]
    pub volume_change_ratio_5m: Option<String>,
    #[serde(rename = "tr5m", skip_serializing_if = "Option::is_none")]
    pub trades_5m: Option<i64>,
    #[serde(rename = "dpc5m", skip_serializing_if = "Option::is_none")]
    pub dapp_program_count_5m: Option<i32>,
    #[serde(rename = "pc5m", skip_serializing_if = "Option::is_none")]
    pub pool_count_5m: Option<i32>,
    #[serde(rename = "liq5m", skip_serializing_if = "Option::is_none")]
    pub liquidity_in_usd_5m: Option<String>,
    #[serde(rename = "lpc5m", skip_serializing_if = "Option::is_none")]
    pub liquidity_change_ratio_5m: Option<String>,

    // 15-minute data
    #[serde(rename = "b15m", skip_serializing_if = "Option::is_none")]
    pub buys_15m: Option<i64>,
    #[serde(rename = "s15m", skip_serializing_if = "Option::is_none")]
    pub sells_15m: Option<i64>,
    #[serde(rename = "be15m", skip_serializing_if = "Option::is_none")]
    pub buyers_15m: Option<i64>,
    #[serde(rename = "se15m", skip_serializing_if = "Option::is_none")]
    pub sellers_15m: Option<i64>,
    #[serde(rename = "bviu15m", skip_serializing_if = "Option::is_none")]
    pub buy_volume_in_usd_15m: Option<String>,
    #[serde(rename = "sviu15m", skip_serializing_if = "Option::is_none")]
    pub sell_volume_in_usd_15m: Option<String>,
    #[serde(rename = "p15m", skip_serializing_if = "Option::is_none")]
    pub price_15m: Option<String>,
    #[serde(rename = "oiu15m", skip_serializing_if = "Option::is_none")]
    pub open_in_usd_15m: Option<String>,
    #[serde(rename = "ciu15m", skip_serializing_if = "Option::is_none")]
    pub close_in_usd_15m: Option<String>,
    #[serde(rename = "vpc15m", skip_serializing_if = "Option::is_none")]
    pub volume_change_ratio_15m: Option<String>,
    #[serde(rename = "tr15m", skip_serializing_if = "Option::is_none")]
    pub trades_15m: Option<i64>,
    #[serde(rename = "dpc15m", skip_serializing_if = "Option::is_none")]
    pub dapp_program_count_15m: Option<i32>,
    #[serde(rename = "pc15m", skip_serializing_if = "Option::is_none")]
    pub pool_count_15m: Option<i32>,
    #[serde(rename = "liq15m", skip_serializing_if = "Option::is_none")]
    pub liquidity_in_usd_15m: Option<String>,
    #[serde(rename = "lpc15m", skip_serializing_if = "Option::is_none")]
    pub liquidity_change_ratio_15m: Option<String>,

    // 30-minute data
    #[serde(rename = "b30m", skip_serializing_if = "Option::is_none")]
    pub buys_30m: Option<i64>,
    #[serde(rename = "s30m", skip_serializing_if = "Option::is_none")]
    pub sells_30m: Option<i64>,
    #[serde(rename = "be30m", skip_serializing_if = "Option::is_none")]
    pub buyers_30m: Option<i64>,
    #[serde(rename = "se30m", skip_serializing_if = "Option::is_none")]
    pub sellers_30m: Option<i64>,
    #[serde(rename = "bviu30m", skip_serializing_if = "Option::is_none")]
    pub buy_volume_in_usd_30m: Option<String>,
    #[serde(rename = "sviu30m", skip_serializing_if = "Option::is_none")]
    pub sell_volume_in_usd_30m: Option<String>,
    #[serde(rename = "p30m", skip_serializing_if = "Option::is_none")]
    pub price_30m: Option<String>,
    #[serde(rename = "oiu30m", skip_serializing_if = "Option::is_none")]
    pub open_in_usd_30m: Option<String>,
    #[serde(rename = "ciu30m", skip_serializing_if = "Option::is_none")]
    pub close_in_usd_30m: Option<String>,
    #[serde(rename = "vpc30m", skip_serializing_if = "Option::is_none")]
    pub volume_change_ratio_30m: Option<String>,
    #[serde(rename = "tr30m", skip_serializing_if = "Option::is_none")]
    pub trades_30m: Option<i64>,
    #[serde(rename = "dpc30m", skip_serializing_if = "Option::is_none")]
    pub dapp_program_count_30m: Option<i32>,
    #[serde(rename = "pc30m", skip_serializing_if = "Option::is_none")]
    pub pool_count_30m: Option<i32>,
    #[serde(rename = "liq30m", skip_serializing_if = "Option::is_none")]
    pub liquidity_in_usd_30m: Option<String>,
    #[serde(rename = "lpc30m", skip_serializing_if = "Option::is_none")]
    pub liquidity_change_ratio_30m: Option<String>,

    // 1-hour data
    #[serde(rename = "b1h", skip_serializing_if = "Option::is_none")]
    pub buys_1h: Option<i64>,
    #[serde(rename = "s1h", skip_serializing_if = "Option::is_none")]
    pub sells_1h: Option<i64>,
    #[serde(rename = "be1h", skip_serializing_if = "Option::is_none")]
    pub buyers_1h: Option<i64>,
    #[serde(rename = "se1h", skip_serializing_if = "Option::is_none")]
    pub sellers_1h: Option<i64>,
    #[serde(rename = "bviu1h", skip_serializing_if = "Option::is_none")]
    pub buy_volume_in_usd_1h: Option<String>,
    #[serde(rename = "sviu1h", skip_serializing_if = "Option::is_none")]
    pub sell_volume_in_usd_1h: Option<String>,
    #[serde(rename = "p1h", skip_serializing_if = "Option::is_none")]
    pub price_1h: Option<String>,
    #[serde(rename = "oiu1h", skip_serializing_if = "Option::is_none")]
    pub open_in_usd_1h: Option<String>,
    #[serde(rename = "ciu1h", skip_serializing_if = "Option::is_none")]
    pub close_in_usd_1h: Option<String>,
    #[serde(rename = "vpc1h", skip_serializing_if = "Option::is_none")]
    pub volume_change_ratio_1h: Option<String>,
    #[serde(rename = "tr1h", skip_serializing_if = "Option::is_none")]
    pub trades_1h: Option<i64>,
    #[serde(rename = "dpc1h", skip_serializing_if = "Option::is_none")]
    pub dapp_program_count_1h: Option<i32>,
    #[serde(rename = "pc1h", skip_serializing_if = "Option::is_none")]
    pub pool_count_1h: Option<i32>,
    #[serde(rename = "liq1h", skip_serializing_if = "Option::is_none")]
    pub liquidity_in_usd_1h: Option<String>,
    #[serde(rename = "lpc1h", skip_serializing_if = "Option::is_none")]
    pub liquidity_change_ratio_1h: Option<String>,

    // 2-hour data
    #[serde(rename = "b2h", skip_serializing_if = "Option::is_none")]
    pub buys_2h: Option<i64>,
    #[serde(rename = "s2h", skip_serializing_if = "Option::is_none")]
    pub sells_2h: Option<i64>,
    #[serde(rename = "be2h", skip_serializing_if = "Option::is_none")]
    pub buyers_2h: Option<i64>,
    #[serde(rename = "se2h", skip_serializing_if = "Option::is_none")]
    pub sellers_2h: Option<i64>,
    #[serde(rename = "bviu2h", skip_serializing_if = "Option::is_none")]
    pub buy_volume_in_usd_2h: Option<String>,
    #[serde(rename = "sviu2h", skip_serializing_if = "Option::is_none")]
    pub sell_volume_in_usd_2h: Option<String>,
    #[serde(rename = "p2h", skip_serializing_if = "Option::is_none")]
    pub price_2h: Option<String>,
    #[serde(rename = "oiu2h", skip_serializing_if = "Option::is_none")]
    pub open_in_usd_2h: Option<String>,
    #[serde(rename = "ciu2h", skip_serializing_if = "Option::is_none")]
    pub close_in_usd_2h: Option<String>,
    #[serde(rename = "vpc2h", skip_serializing_if = "Option::is_none")]
    pub volume_change_ratio_2h: Option<String>,
    #[serde(rename = "tr2h", skip_serializing_if = "Option::is_none")]
    pub trades_2h: Option<i64>,
    #[serde(rename = "dpc2h", skip_serializing_if = "Option::is_none")]
    pub dapp_program_count_2h: Option<i32>,
    #[serde(rename = "pc2h", skip_serializing_if = "Option::is_none")]
    pub pool_count_2h: Option<i32>,
    #[serde(rename = "liq2h", skip_serializing_if = "Option::is_none")]
    pub liquidity_in_usd_2h: Option<String>,
    #[serde(rename = "lpc2h", skip_serializing_if = "Option::is_none")]
    pub liquidity_change_ratio_2h: Option<String>,

    // 4-hour data
    #[serde(rename = "b4h", skip_serializing_if = "Option::is_none")]
    pub buys_4h: Option<i64>,
    #[serde(rename = "s4h", skip_serializing_if = "Option::is_none")]
    pub sells_4h: Option<i64>,
    #[serde(rename = "be4h", skip_serializing_if = "Option::is_none")]
    pub buyers_4h: Option<i64>,
    #[serde(rename = "se4h", skip_serializing_if = "Option::is_none")]
    pub sellers_4h: Option<i64>,
    #[serde(rename = "bviu4h", skip_serializing_if = "Option::is_none")]
    pub buy_volume_in_usd_4h: Option<String>,
    #[serde(rename = "sviu4h", skip_serializing_if = "Option::is_none")]
    pub sell_volume_in_usd_4h: Option<String>,
    #[serde(rename = "p4h", skip_serializing_if = "Option::is_none")]
    pub price_4h: Option<String>,
    #[serde(rename = "oiu4h", skip_serializing_if = "Option::is_none")]
    pub open_in_usd_4h: Option<String>,
    #[serde(rename = "ciu4h", skip_serializing_if = "Option::is_none")]
    pub close_in_usd_4h: Option<String>,
    #[serde(rename = "vpc4h", skip_serializing_if = "Option::is_none")]
    pub volume_change_ratio_4h: Option<String>,
    #[serde(rename = "tr4h", skip_serializing_if = "Option::is_none")]
    pub trades_4h: Option<i64>,
    #[serde(rename = "dpc4h", skip_serializing_if = "Option::is_none")]
    pub dapp_program_count_4h: Option<i32>,
    #[serde(rename = "pc4h", skip_serializing_if = "Option::is_none")]
    pub pool_count_4h: Option<i32>,
    #[serde(rename = "liq4h", skip_serializing_if = "Option::is_none")]
    pub liquidity_in_usd_4h: Option<String>,
    #[serde(rename = "lpc4h", skip_serializing_if = "Option::is_none")]
    pub liquidity_change_ratio_4h: Option<String>,

    // 6-hour data
    #[serde(rename = "b6h", skip_serializing_if = "Option::is_none")]
    pub buys_6h: Option<i64>,
    #[serde(rename = "s6h", skip_serializing_if = "Option::is_none")]
    pub sells_6h: Option<i64>,
    #[serde(rename = "be6h", skip_serializing_if = "Option::is_none")]
    pub buyers_6h: Option<i64>,
    #[serde(rename = "se6h", skip_serializing_if = "Option::is_none")]
    pub sellers_6h: Option<i64>,
    #[serde(rename = "bviu6h", skip_serializing_if = "Option::is_none")]
    pub buy_volume_in_usd_6h: Option<String>,
    #[serde(rename = "sviu6h", skip_serializing_if = "Option::is_none")]
    pub sell_volume_in_usd_6h: Option<String>,
    #[serde(rename = "p6h", skip_serializing_if = "Option::is_none")]
    pub price_6h: Option<String>,
    #[serde(rename = "oiu6h", skip_serializing_if = "Option::is_none")]
    pub open_in_usd_6h: Option<String>,
    #[serde(rename = "ciu6h", skip_serializing_if = "Option::is_none")]
    pub close_in_usd_6h: Option<String>,
    #[serde(rename = "vpc6h", skip_serializing_if = "Option::is_none")]
    pub volume_change_ratio_6h: Option<String>,
    #[serde(rename = "tr6h", skip_serializing_if = "Option::is_none")]
    pub trades_6h: Option<i64>,
    #[serde(rename = "dpc6h", skip_serializing_if = "Option::is_none")]
    pub dapp_program_count_6h: Option<i32>,
    #[serde(rename = "pc6h", skip_serializing_if = "Option::is_none")]
    pub pool_count_6h: Option<i32>,
    #[serde(rename = "liq6h", skip_serializing_if = "Option::is_none")]
    pub liquidity_in_usd_6h: Option<String>,
    #[serde(rename = "lpc6h", skip_serializing_if = "Option::is_none")]
    pub liquidity_change_ratio_6h: Option<String>,

    // 8-hour data
    #[serde(rename = "b8h", skip_serializing_if = "Option::is_none")]
    pub buys_8h: Option<i64>,
    #[serde(rename = "s8h", skip_serializing_if = "Option::is_none")]
    pub sells_8h: Option<i64>,
    #[serde(rename = "be8h", skip_serializing_if = "Option::is_none")]
    pub buyers_8h: Option<i64>,
    #[serde(rename = "se8h", skip_serializing_if = "Option::is_none")]
    pub sellers_8h: Option<i64>,
    #[serde(rename = "bviu8h", skip_serializing_if = "Option::is_none")]
    pub buy_volume_in_usd_8h: Option<String>,
    #[serde(rename = "sviu8h", skip_serializing_if = "Option::is_none")]
    pub sell_volume_in_usd_8h: Option<String>,
    #[serde(rename = "p8h", skip_serializing_if = "Option::is_none")]
    pub price_8h: Option<String>,
    #[serde(rename = "oiu8h", skip_serializing_if = "Option::is_none")]
    pub open_in_usd_8h: Option<String>,
    #[serde(rename = "ciu8h", skip_serializing_if = "Option::is_none")]
    pub close_in_usd_8h: Option<String>,
    #[serde(rename = "vpc8h", skip_serializing_if = "Option::is_none")]
    pub volume_change_ratio_8h: Option<String>,
    #[serde(rename = "tr8h", skip_serializing_if = "Option::is_none")]
    pub trades_8h: Option<i64>,
    #[serde(rename = "dpc8h", skip_serializing_if = "Option::is_none")]
    pub dapp_program_count_8h: Option<i32>,
    #[serde(rename = "pc8h", skip_serializing_if = "Option::is_none")]
    pub pool_count_8h: Option<i32>,
    #[serde(rename = "liq8h", skip_serializing_if = "Option::is_none")]
    pub liquidity_in_usd_8h: Option<String>,
    #[serde(rename = "lpc8h", skip_serializing_if = "Option::is_none")]
    pub liquidity_change_ratio_8h: Option<String>,

    // 24-hour data
    #[serde(rename = "b24h", skip_serializing_if = "Option::is_none")]
    pub buys_24h: Option<i64>,
    #[serde(rename = "s24h", skip_serializing_if = "Option::is_none")]
    pub sells_24h: Option<i64>,
    #[serde(rename = "be24h", skip_serializing_if = "Option::is_none")]
    pub buyers_24h: Option<i64>,
    #[serde(rename = "se24h", skip_serializing_if = "Option::is_none")]
    pub sellers_24h: Option<i64>,
    #[serde(rename = "bviu24h", skip_serializing_if = "Option::is_none")]
    pub buy_volume_in_usd_24h: Option<String>,
    #[serde(rename = "sviu24h", skip_serializing_if = "Option::is_none")]
    pub sell_volume_in_usd_24h: Option<String>,
    #[serde(rename = "p24h", skip_serializing_if = "Option::is_none")]
    pub price_24h: Option<String>,
    #[serde(rename = "oiu24h", skip_serializing_if = "Option::is_none")]
    pub open_in_usd_24h: Option<String>,
    #[serde(rename = "ciu24h", skip_serializing_if = "Option::is_none")]
    pub close_in_usd_24h: Option<String>,
    #[serde(rename = "vpc24h", skip_serializing_if = "Option::is_none")]
    pub volume_change_ratio_24h: Option<String>,
    #[serde(rename = "tr24h", skip_serializing_if = "Option::is_none")]
    pub trades_24h: Option<i64>,
    #[serde(rename = "dpc24h", skip_serializing_if = "Option::is_none")]
    pub dapp_program_count_24h: Option<i32>,
    #[serde(rename = "pc24h", skip_serializing_if = "Option::is_none")]
    pub pool_count_24h: Option<i32>,
    #[serde(rename = "liq24h", skip_serializing_if = "Option::is_none")]
    pub liquidity_in_usd_24h: Option<String>,
    #[serde(rename = "lpc24h", skip_serializing_if = "Option::is_none")]
    pub liquidity_change_ratio_24h: Option<String>,

    // 1-week data (note: JSON keys use uppercase W)
    #[serde(rename = "b1W", skip_serializing_if = "Option::is_none")]
    pub buys_1w: Option<i64>,
    #[serde(rename = "s1W", skip_serializing_if = "Option::is_none")]
    pub sells_1w: Option<i64>,
    #[serde(rename = "be1W", skip_serializing_if = "Option::is_none")]
    pub buyers_1w: Option<i64>,
    #[serde(rename = "se1W", skip_serializing_if = "Option::is_none")]
    pub sellers_1w: Option<i64>,
    #[serde(rename = "bviu1W", skip_serializing_if = "Option::is_none")]
    pub buy_volume_in_usd_1w: Option<String>,
    #[serde(rename = "sviu1W", skip_serializing_if = "Option::is_none")]
    pub sell_volume_in_usd_1w: Option<String>,
    #[serde(rename = "p1W", skip_serializing_if = "Option::is_none")]
    pub price_1w: Option<String>,
    #[serde(rename = "oiu1W", skip_serializing_if = "Option::is_none")]
    pub open_in_usd_1w: Option<String>,
    #[serde(rename = "ciu1W", skip_serializing_if = "Option::is_none")]
    pub close_in_usd_1w: Option<String>,
    #[serde(rename = "vpc1W", skip_serializing_if = "Option::is_none")]
    pub volume_change_ratio_1w: Option<String>,
    #[serde(rename = "tr1W", skip_serializing_if = "Option::is_none")]
    pub trades_1w: Option<i64>,
    #[serde(rename = "dpc1W", skip_serializing_if = "Option::is_none")]
    pub dapp_program_count_1w: Option<i32>,
    #[serde(rename = "pc1W", skip_serializing_if = "Option::is_none")]
    pub pool_count_1w: Option<i32>,
    #[serde(rename = "liq1W", skip_serializing_if = "Option::is_none")]
    pub liquidity_in_usd_1w: Option<String>,
    #[serde(rename = "lpc1W", skip_serializing_if = "Option::is_none")]
    pub liquidity_change_ratio_1w: Option<String>,

    // 1-month data (note: JSON keys use uppercase M)
    #[serde(rename = "b1M", skip_serializing_if = "Option::is_none")]
    pub buys_1mo: Option<i64>,
    #[serde(rename = "s1M", skip_serializing_if = "Option::is_none")]
    pub sells_1mo: Option<i64>,
    #[serde(rename = "be1M", skip_serializing_if = "Option::is_none")]
    pub buyers_1mo: Option<i64>,
    #[serde(rename = "se1M", skip_serializing_if = "Option::is_none")]
    pub sellers_1mo: Option<i64>,
    #[serde(rename = "bviu1M", skip_serializing_if = "Option::is_none")]
    pub buy_volume_in_usd_1mo: Option<String>,
    #[serde(rename = "sviu1M", skip_serializing_if = "Option::is_none")]
    pub sell_volume_in_usd_1mo: Option<String>,
    #[serde(rename = "p1M", skip_serializing_if = "Option::is_none")]
    pub price_1mo: Option<String>,
    #[serde(rename = "oiu1M", skip_serializing_if = "Option::is_none")]
    pub open_in_usd_1mo: Option<String>,
    #[serde(rename = "ciu1M", skip_serializing_if = "Option::is_none")]
    pub close_in_usd_1mo: Option<String>,
    #[serde(rename = "vpc1M", skip_serializing_if = "Option::is_none")]
    pub volume_change_ratio_1mo: Option<String>,
    #[serde(rename = "tr1M", skip_serializing_if = "Option::is_none")]
    pub trades_1mo: Option<i64>,
    #[serde(rename = "dpc1M", skip_serializing_if = "Option::is_none")]
    pub dapp_program_count_1mo: Option<i32>,
    #[serde(rename = "pc1M", skip_serializing_if = "Option::is_none")]
    pub pool_count_1mo: Option<i32>,
    #[serde(rename = "liq1M", skip_serializing_if = "Option::is_none")]
    pub liquidity_in_usd_1mo: Option<String>,
    #[serde(rename = "lpc1M", skip_serializing_if = "Option::is_none")]
    pub liquidity_change_ratio_1mo: Option<String>,
}

/// Token holder information
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenHolder {
    pub token_address: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub holders: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub top100_amount: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub top10_amount: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub top100_holders: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub top10_holders: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub top100_ratio: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub top10_ratio: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub creators_holders: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub creators_amount: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub creators_ratio: Option<String>,
    pub timestamp: i64,
}

/// Wallet balance data
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WalletBalance {
    pub wallet_address: String,
    pub token_address: String,
    pub token_price_in_usd: String,
    pub balance: String,
    pub timestamp: i64,
}

/// Wallet profit and loss data
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WalletPnl {
    pub wallet_address: String,
    pub buys: i32,
    pub buy_amount: String,
    pub buy_amount_in_usd: String,
    pub average_buy_price_in_usd: String,
    pub sell_amount: String,
    pub sell_amount_in_usd: String,
    pub sells: i32,
    pub wins: i32,
    pub win_ratio: String,
    pub pnl_in_usd: String,
    pub average_pnl_in_usd: String,
    pub pnl_ratio: String,
    pub profitable_days: i32,
    pub losing_days: i32,
    pub tokens: i32,
    pub resolution: String,
}

/// DEX protocol information
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DexProtocol {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub program_address: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub protocol_family: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub protocol_name: Option<String>,
}

/// New token data
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NewToken {
    pub token_address: String,
    pub name: String,
    pub symbol: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub decimals: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub image_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub social_media: Option<SocialMedia>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub coingecko_coin_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub launch_from: Option<DexProtocol>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub migrated_to: Option<DexProtocol>,
    pub created_at_ms: i64,
}

/// Token supply data
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenSupply {
    pub token_address: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub supply: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub market_cap_in_usd: Option<String>,
    pub timestamp: i64,
}

/// DEX pool balance data
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DexPoolBalance {
    pub pool_address: String,
    pub token_a_address: String,
    pub token_a_liquidity_in_usd: String,
    pub token_b_address: String,
    pub token_b_liquidity_in_usd: String,
}

/// Token liquidity data
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenLiquidity {
    pub token_address: String,
    pub metric_type: MetricType,
    pub value: String,
    pub timestamp: i64,
}

/// Token max liquidity data (max liquidity in a single pool)
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenMaxLiquidity {
    pub token_address: String,
    pub pool_address: String,
    pub liquidity_in_usd: String,
    pub liquidity_in_native: String,
    pub timestamp: i64,
}

/// Token total liquidity data (total liquidity across all pools)
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenTotalLiquidity {
    pub token_address: String,
    pub liquidity_in_usd: String,
    pub liquidity_in_native: String,
    pub pool_count: i32,
    pub timestamp: i64,
}

/// Token bonding curve data
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenBondingCurve {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub token_address: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub progress_ratio: Option<String>,
}

/// Social media links
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct SocialMedia {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub twitter: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub telegram: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub website: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tiktok: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub discord: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub facebook: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub github: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub instagram: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub linkedin: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub medium: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reddit: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub youtube: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bitbucket: Option<String>,
}

/// Token metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenMetadata {
    pub token_address: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub decimals: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub symbol: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub image_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub social_media: Option<SocialMedia>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub created_at_ms: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub coingecko_coin_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub launch_from: Option<DexProtocol>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub migrated_to: Option<DexProtocol>,
}

/// Price type for candle data
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PriceType {
    #[default]
    Usd,
    Native,
}

/// Candlestick data for Token/Pool/Pair
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Candle {
    pub address: String,
    pub open: String,
    pub close: String,
    pub high: String,
    pub low: String,
    pub volume: String,
    pub resolution: String,
    pub time: i64,
    pub number: i32,
}

/// Alias for backward compatibility
pub type TokenCandle = Candle;

/// Trade activity data
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TradeActivity {
    pub token_address: String,
    pub timestamp: i64,
    pub kind: String,
    pub buy_amount: String,
    pub buy_amount_in_usd: String,
    pub buy_token_address: String,
    pub buy_token_name: String,
    pub buy_token_symbol: String,
    pub buy_wallet_address: String,
    pub sell_amount: String,
    pub sell_amount_in_usd: String,
    pub sell_token_address: String,
    pub sell_token_name: String,
    pub sell_token_symbol: String,
    pub sell_wallet_address: String,
    pub tx_hash: String,
}

/// Wallet token profit and loss data
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WalletTokenPnl {
    pub wallet_address: String,
    pub token_address: String,
    pub timestamp: i64,
    pub buy_count: i32,
    pub buy_count_30d: i32,
    pub buy_count_7d: i32,
    pub sell_count: i32,
    pub sell_count_30d: i32,
    pub sell_count_7d: i32,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub token_price_in_usd: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub open_time: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last_time: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub close_time: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub buy_amount: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub buy_amount_in_usd: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sell_amount: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sell_amount_in_usd: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub held_duration_timestamp: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub average_buy_price_in_usd: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub average_sell_price_in_usd: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub unrealized_profit_in_usd: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub unrealized_profit_ratio: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub realized_profit_in_usd: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub realized_profit_ratio: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub total_realized_profit_in_usd: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub total_realized_profit_ratio: Option<String>,
}

/// Ranking token list data
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RankingTokenList {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<TokenMetadata>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub holder: Option<TokenHolder>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub supply: Option<TokenSupply>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub stat: Option<TokenStat>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bonding_curve: Option<TokenBondingCurve>,
}
