package com.bigcrunch.ads.models import com.bigcrunch.ads.core.DeviceContextData import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import java.util.UUID // MARK: - Legacy Event (keeping for backward compatibility) /** * Analytics event sent to BigCrunch backend * * All ad-related events (impressions, clicks, revenue) are tracked and sent * to the analytics endpoint in this format. */ @JsonClass(generateAdapter = true) internal data class AdEvent( @Json(name = "eventType") val eventType: String, // "screen_view", "ad_request", "ad_impression", "ad_revenue", "ad_click", "ad_viewable" @Json(name = "placementId") val placementId: String? = null, @Json(name = "format") val format: String? = null, // "banner", "interstitial", "rewarded" @Json(name = "timestamp") val timestamp: Long = System.currentTimeMillis(), @Json(name = "sessionId") val sessionId: String, @Json(name = "deviceInfo") val deviceInfo: DeviceInfo, @Json(name = "revenue") val revenue: RevenueData? = null ) /** * Device and app information included with every event */ @JsonClass(generateAdapter = true) internal data class DeviceInfo( @Json(name = "platform") val platform: String = "android", @Json(name = "osVersion") val osVersion: String, @Json(name = "appVersion") val appVersion: String, @Json(name = "deviceId") val deviceId: String ) /** * Revenue data from ILRD (Impression-Level Revenue Data) * * Only present in ad_revenue events. Values are in micros (1/1,000,000 of currency unit). */ @JsonClass(generateAdapter = true) internal data class RevenueData( @Json(name = "valueMicros") val valueMicros: Long, @Json(name = "currency") val currency: String ) // MARK: - Screen View Options /** * Options for customizing screen view tracking * * Allows app developers to provide page URL, content metadata, * and custom dimensions for analytics events. */ data class ScreenViewOptions( /** Override the auto-generated page URL (must be a valid URL) */ val pageUrl: String? = null, /** Content metadata for this screen/page */ val pageMeta: PageMetaData? = null, /** Custom key-value dimensions attached to analytics events */ val customDimensions: Map? = null ) /** * Content metadata for a screen/page view * * Matches the `page_meta_data` fields in the analytics schema. */ @JsonClass(generateAdapter = true) data class PageMetaData( /** Canonical page URL */ @Json(name = "url") val url: String? = null, /** Content author */ @Json(name = "author") val author: String? = null, /** Page/screen title */ @Json(name = "title") val title: String? = null, /** Featured image URL */ @Json(name = "thumbnailUrl") val thumbnailUrl: String? = null, /** Content section/category */ @Json(name = "articleSection") val articleSection: String? = null, /** Comma-separated keywords */ @Json(name = "keywords") val keywords: String? = null, /** Content creation date (ISO 8601) */ @Json(name = "dateCreated") val dateCreated: String? = null, /** Content last modified date (ISO 8601) */ @Json(name = "dateModified") val dateModified: String? = null, /** Content publication date (ISO 8601) */ @Json(name = "datePublished") val datePublished: String? = null ) // MARK: - Enhanced Analytics Events (matching web SDK data model) /** * Screen/Page view event - matches `/pageviews` endpoint * * Sent when a new screen is viewed. Contains session context and device info. */ @JsonClass(generateAdapter = true) internal data class PageViewEvent( // Web schema common fields @Json(name = "payload_version") val payloadVersion: String, @Json(name = "config_version") val configVersion: Int = 1, @Json(name = "browser_timestamp") val browserTimestamp: String, // ISO 8601 // Session/user context @Json(name = "session_id") val sessionId: String, @Json(name = "user_id") val userId: String, @Json(name = "property_id") val propertyId: String, @Json(name = "new_user") val newUser: Boolean, // Page context @Json(name = "page_id") val pageId: String, @Json(name = "session_depth") val sessionDepth: Int, @Json(name = "page_url") val pageUrl: String, @Json(name = "page_search") val pageSearch: String = "", @Json(name = "page_referrer") val pageReferrer: String = "", // Device context (flattened) @Json(name = "browser") val browser: String, @Json(name = "device") val device: String, @Json(name = "os") val os: String, @Json(name = "country") val country: String, @Json(name = "region") val region: String, // Attribution @Json(name = "session_source") val sessionSource: String, @Json(name = "session_medium") val sessionMedium: String, @Json(name = "utm_source") val utmSource: String = "", @Json(name = "utm_medium") val utmMedium: String = "", @Json(name = "utm_campaign") val utmCampaign: String = "", @Json(name = "utm_term") val utmTerm: String = "", @Json(name = "utm_content") val utmContent: String = "", // Click IDs for attribution @Json(name = "gclid") val gclid: String = "", @Json(name = "fbclid") val fbclid: String = "", // Account & Identity @Json(name = "acct_type") val acctType: String = "anonymous", @Json(name = "dii_source") val diiSource: String = "", // Ad platform IDs @Json(name = "gam_network_code") val gamNetworkCode: String = "", @Json(name = "amzn_pub_id") val amznPubId: String = "", // Custom dimensions @Json(name = "custom_dimensions") val customDimensions: Map = emptyMap(), // Page metadata (only for pageview events) @Json(name = "page_meta_data") val pageMetaData: PageMetaData? = null ) /** * Individual impression record - nested inside ImpressionBatchEvent * * Represents a single ad impression with auction/bid data. * All string fields default to empty string to ensure they're serialized (server requires all fields). */ @JsonClass(generateAdapter = true) internal data class ImpressionRecord( /** Placement identifier (slot_id in web SDK) */ @Json(name = "slot_id") val slotId: String, /** GAM ad unit path */ @Json(name = "gam_unit") val gamUnit: String, /** GAM price bucket */ @Json(name = "gam_price_bucket") val gamPriceBucket: String = "", /** Unique identifier for this impression */ @Json(name = "impression_id") val impressionId: String, /** Auction ID (from Prebid) */ @Json(name = "auction_id") val auctionId: String = "", /** Refresh count for this placement */ @Json(name = "refresh_count") val refreshCount: Int = 0, /** Winning bidder name */ @Json(name = "ad_bidder") val adBidder: String = "", /** Ad size (e.g., "320x50") */ @Json(name = "ad_size") val adSize: String = "", /** Bid price/revenue in CPM */ @Json(name = "ad_price") val adPrice: Double = 0.0, /** Floor price */ @Json(name = "ad_floor_price") val adFloorPrice: Double = 0.0, /** Minimum bid to win */ @Json(name = "min_bid_to_win") val minBidToWin: Double = 0.0, /** Advertiser ID from GAM */ @Json(name = "advertiser_id") val advertiserId: String = "", /** Campaign ID from GAM */ @Json(name = "campaign_id") val campaignId: String = "", /** Line item ID from GAM */ @Json(name = "line_item_id") val lineItemId: String = "", /** Creative ID from GAM response */ @Json(name = "creative_id") val creativeId: String = "", /** Amazon bid data (if applicable) */ @Json(name = "ad_amznbid") val adAmznbid: String = "", /** Amazon price data (if applicable) */ @Json(name = "ad_amznp") val adAmznp: String = "", /** Demand type (banner, video, etc.) */ @Json(name = "ad_demand_type") val adDemandType: String = "", /** Demand channel (e.g., "Prebid Header", "GAM Direct") */ @Json(name = "demand_channel") val demandChannel: String = "", /** Slot-level custom dimensions */ @Json(name = "custom_dimensions") val customDimensions: Map = emptyMap() ) /** * Impression batch event - matches `/impressions` endpoint * * Contains session/page context with nested array of impression records. * This matches the web SDK format where impressions are batched with pageview context. */ @JsonClass(generateAdapter = true) internal data class ImpressionBatchEvent( // Web schema common fields @Json(name = "payload_version") val payloadVersion: String, @Json(name = "config_version") val configVersion: Int = 1, @Json(name = "browser_timestamp") val browserTimestamp: String, // ISO 8601 // Session/user context @Json(name = "session_id") val sessionId: String, @Json(name = "user_id") val userId: String, @Json(name = "property_id") val propertyId: String, @Json(name = "new_user") val newUser: Boolean, @Json(name = "page_id") val pageId: String, @Json(name = "session_depth") val sessionDepth: Int, @Json(name = "page_url") val pageUrl: String = "", @Json(name = "page_search") val pageSearch: String = "", @Json(name = "page_referrer") val pageReferrer: String = "", // Device context (flattened) @Json(name = "browser") val browser: String, @Json(name = "device") val device: String, @Json(name = "os") val os: String, @Json(name = "country") val country: String, @Json(name = "region") val region: String, // Attribution @Json(name = "session_source") val sessionSource: String, @Json(name = "session_medium") val sessionMedium: String, @Json(name = "utm_source") val utmSource: String = "", @Json(name = "utm_medium") val utmMedium: String = "", @Json(name = "utm_campaign") val utmCampaign: String = "", @Json(name = "utm_term") val utmTerm: String = "", @Json(name = "utm_content") val utmContent: String = "", // Click IDs for attribution @Json(name = "gclid") val gclid: String = "", @Json(name = "fbclid") val fbclid: String = "", // Account & Identity @Json(name = "acct_type") val acctType: String = "anonymous", @Json(name = "dii_source") val diiSource: String = "", // Ad platform IDs @Json(name = "gam_network_code") val gamNetworkCode: String = "", @Json(name = "amzn_pub_id") val amznPubId: String = "", // Custom dimensions @Json(name = "custom_dimensions") val customDimensions: Map = emptyMap(), // Nested impressions array @Json(name = "impressions") val impressions: List ) /** * Ad click event - matches `/clicks` endpoint * * Sent when a user clicks on an ad. */ @JsonClass(generateAdapter = true) internal data class ClickEvent( // Web schema common fields @Json(name = "payload_version") val payloadVersion: String, @Json(name = "config_version") val configVersion: Int = 1, @Json(name = "browser_timestamp") val browserTimestamp: String, // ISO 8601 // Session/user context @Json(name = "session_id") val sessionId: String, @Json(name = "user_id") val userId: String, @Json(name = "property_id") val propertyId: String, @Json(name = "new_user") val newUser: Boolean, @Json(name = "page_id") val pageId: String, @Json(name = "session_depth") val sessionDepth: Int, @Json(name = "page_url") val pageUrl: String = "", @Json(name = "page_search") val pageSearch: String = "", @Json(name = "page_referrer") val pageReferrer: String = "", // Device context (flattened) @Json(name = "browser") val browser: String, @Json(name = "device") val device: String, @Json(name = "os") val os: String, @Json(name = "country") val country: String, @Json(name = "region") val region: String, // Attribution @Json(name = "session_source") val sessionSource: String, @Json(name = "session_medium") val sessionMedium: String, @Json(name = "utm_source") val utmSource: String = "", @Json(name = "utm_medium") val utmMedium: String = "", @Json(name = "utm_campaign") val utmCampaign: String = "", @Json(name = "utm_term") val utmTerm: String = "", @Json(name = "utm_content") val utmContent: String = "", // Click IDs for attribution @Json(name = "gclid") val gclid: String = "", @Json(name = "fbclid") val fbclid: String = "", // Account & Identity @Json(name = "acct_type") val acctType: String = "anonymous", @Json(name = "dii_source") val diiSource: String = "", // Ad platform IDs @Json(name = "gam_network_code") val gamNetworkCode: String = "", @Json(name = "amzn_pub_id") val amznPubId: String = "", // Custom dimensions @Json(name = "custom_dimensions") val customDimensions: Map = emptyMap(), // Click-specific fields (nested click object per schema) @Json(name = "click") val click: ClickData ) /** * Click data object nested inside ClickEvent */ @JsonClass(generateAdapter = true) internal data class ClickData( @Json(name = "click_id") val clickId: String, @Json(name = "slot_id") val slotId: String, @Json(name = "impression_id") val impressionId: String, @Json(name = "refresh_count") val refreshCount: Int = 0, @Json(name = "ad_amznp") val adAmznp: String = "", @Json(name = "ad_bidder") val adBidder: String = "", @Json(name = "ad_size") val adSize: String = "", @Json(name = "advertiser_id") val advertiserId: String = "", @Json(name = "campaign_id") val campaignId: String = "", @Json(name = "line_item_id") val lineItemId: String = "", @Json(name = "creative_id") val creativeId: String = "", @Json(name = "ad_demand_type") val adDemandType: String = "", @Json(name = "demand_channel") val demandChannel: String = "", /** Custom dimensions - values must be string arrays per backend schema */ @Json(name = "custom_dimensions") val customDimensions: Map> = emptyMap() ) /** * Viewability event - matches `/viewability` endpoint * * Sent when an ad meets viewability thresholds (e.g., 50% visible for 1 second). */ @JsonClass(generateAdapter = true) internal data class ViewabilityEvent( // Web schema common fields @Json(name = "payload_version") val payloadVersion: String, @Json(name = "config_version") val configVersion: Int = 1, @Json(name = "browser_timestamp") val browserTimestamp: String, // ISO 8601 // Session/user context @Json(name = "session_id") val sessionId: String, @Json(name = "user_id") val userId: String, @Json(name = "property_id") val propertyId: String, @Json(name = "new_user") val newUser: Boolean, @Json(name = "page_id") val pageId: String, @Json(name = "session_depth") val sessionDepth: Int, @Json(name = "page_url") val pageUrl: String = "", @Json(name = "page_search") val pageSearch: String = "", @Json(name = "page_referrer") val pageReferrer: String = "", // Device context (flattened) @Json(name = "browser") val browser: String, @Json(name = "device") val device: String, @Json(name = "os") val os: String, @Json(name = "country") val country: String, @Json(name = "region") val region: String, // Attribution @Json(name = "session_source") val sessionSource: String, @Json(name = "session_medium") val sessionMedium: String, @Json(name = "utm_source") val utmSource: String = "", @Json(name = "utm_medium") val utmMedium: String = "", @Json(name = "utm_campaign") val utmCampaign: String = "", @Json(name = "utm_term") val utmTerm: String = "", @Json(name = "utm_content") val utmContent: String = "", // Click IDs for attribution @Json(name = "gclid") val gclid: String = "", @Json(name = "fbclid") val fbclid: String = "", // Account & Identity @Json(name = "acct_type") val acctType: String = "anonymous", @Json(name = "dii_source") val diiSource: String = "", // Ad platform IDs @Json(name = "gam_network_code") val gamNetworkCode: String = "", @Json(name = "amzn_pub_id") val amznPubId: String = "", // Custom dimensions @Json(name = "custom_dimensions") val customDimensions: Map = emptyMap(), // Viewability-specific fields (nested array per schema) @Json(name = "viewability") val viewability: List ) /** * Viewability data object nested inside ViewabilityEvent */ @JsonClass(generateAdapter = true) internal data class ViewabilityData( @Json(name = "slot_id") val slotId: String, @Json(name = "impression_id") val impressionId: String, @Json(name = "refresh_count") val refreshCount: Int = 0, @Json(name = "ad_amznp") val adAmznp: String = "", @Json(name = "ad_bidder") val adBidder: String = "", @Json(name = "ad_size") val adSize: String = "", @Json(name = "advertiser_id") val advertiserId: String = "", @Json(name = "campaign_id") val campaignId: String = "", @Json(name = "line_item_id") val lineItemId: String = "", @Json(name = "creative_id") val creativeId: String = "", @Json(name = "ad_demand_type") val adDemandType: String = "", @Json(name = "demand_channel") val demandChannel: String = "", /** Custom dimensions - values must be string arrays per backend schema */ @Json(name = "custom_dimensions") val customDimensions: Map> = emptyMap() ) /** * Engagement event - matches `/engagement` endpoint * * Tracks user engagement with content/screens. */ @JsonClass(generateAdapter = true) internal data class EngagementEvent( // Web schema common fields @Json(name = "payload_version") val payloadVersion: String, @Json(name = "config_version") val configVersion: Int = 1, @Json(name = "browser_timestamp") val browserTimestamp: String, // ISO 8601 // Session/user context @Json(name = "session_id") val sessionId: String, @Json(name = "user_id") val userId: String, @Json(name = "property_id") val propertyId: String, @Json(name = "new_user") val newUser: Boolean, @Json(name = "page_id") val pageId: String, @Json(name = "session_depth") val sessionDepth: Int, @Json(name = "page_url") val pageUrl: String = "", @Json(name = "page_search") val pageSearch: String = "", @Json(name = "page_referrer") val pageReferrer: String = "", // Device context (flattened) @Json(name = "browser") val browser: String, @Json(name = "device") val device: String, @Json(name = "os") val os: String, @Json(name = "country") val country: String, @Json(name = "region") val region: String, // Attribution @Json(name = "session_source") val sessionSource: String, @Json(name = "session_medium") val sessionMedium: String, @Json(name = "utm_source") val utmSource: String = "", @Json(name = "utm_medium") val utmMedium: String = "", @Json(name = "utm_campaign") val utmCampaign: String = "", @Json(name = "utm_term") val utmTerm: String = "", @Json(name = "utm_content") val utmContent: String = "", // Click IDs for attribution @Json(name = "gclid") val gclid: String = "", @Json(name = "fbclid") val fbclid: String = "", // Account & Identity @Json(name = "acct_type") val acctType: String = "anonymous", @Json(name = "dii_source") val diiSource: String = "", // Ad platform IDs @Json(name = "gam_network_code") val gamNetworkCode: String = "", @Json(name = "amzn_pub_id") val amznPubId: String = "", /** Custom dimensions - values must be string arrays per backend schema */ @Json(name = "custom_dimensions") val customDimensions: Map> = emptyMap(), // Engagement-specific fields /** Time actively engaged in seconds */ @Json(name = "engaged_time") val engagedTime: Int, /** Total time on page/screen in seconds */ @Json(name = "time_on_page") val timeOnPage: Int, /** Maximum scroll depth percentage (0-100) */ @Json(name = "scroll_depth") val scrollDepth: Int = 0 ) // MARK: - Auction Data /** * Auction data from Prebid response * * Captures bidding information for analytics. */ internal data class AuctionData( /** Prebid auction ID */ val auctionId: String? = null, /** Winning bidder name */ val bidder: String? = null, /** Winning bid price in CPM */ val bidPriceCpm: Double? = null, /** Creative ID from winning bid */ val creativeId: String? = null, /** GAM price bucket (hb_pb value from targeting KVPs) */ val gamPriceBucket: String? = null, /** Floor price sent to bidders */ val floorPrice: Double? = null, /** Second-highest bid price (for computing min_bid_to_win) */ val secondHighestBid: Double? = null, /** Demand channel ("S2S", "Google Ad Exchange", etc.) */ val demandChannel: String? = null ) { companion object { val EMPTY = AuctionData() } } // MARK: - Impression Context /** * Context for tracking an ad impression * * Aggregates all data needed to track an impression through its lifecycle. */ internal data class ImpressionContext( /** Unique impression identifier */ val impressionId: String = UUID.randomUUID().toString(), /** Placement configuration */ val placementId: String, /** UUID for backend reporting (slot_id) */ val slotId: String, val gamAdUnit: String, val format: String, /** Size (for banners) */ val width: Int? = null, val height: Int? = null, /** Auction data (populated after Prebid response) */ var auctionData: AuctionData = AuctionData.EMPTY, /** Timestamp when impression was created */ val createdAt: Long = System.currentTimeMillis() )