<?php
/**
 * UABB Analytics.
 *
 * @package UABB
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

if ( ! class_exists( 'UABB_Analytics' ) ) {

	/**
	 * Class UABB_Analytics
	 *
	 * Handles analytics-related functionality for the Ultimate Addons for Beaver Builder plugin.
	 *
	 * @since 1.36.16
	 */
	class UABB_Analytics {

		/**
		 * Cached BSF_Analytics_Events instance (null until the library class is loaded).
		 *
		 * @since x.x.x
		 * @var BSF_Analytics_Events|null
		 */
		private static $events = null;

		/**
		 * UABB Analytics constructor.
		 *
		 * Initializing UABB Analytics.
		 *
		 * @since 1.36.16
		 * @access public
		 */
		public function __construct() {

			// BSF Analytics Tracker.
			if ( ! class_exists( 'BSF_Analytics_Loader' ) ) {
				require_once BB_ULTIMATE_ADDON_DIR . 'admin/bsf-analytics/class-bsf-analytics-loader.php';
			}

			$bsf_analytics = BSF_Analytics_Loader::get_instance();

			$bsf_analytics->set_entity(
				array(
					'uabb' => array(
						'product_name'        => 'Ultimate Addons for Beaver Builder',
						'path'                => BB_ULTIMATE_ADDON_DIR . 'admin/bsf-analytics',
						'author'              => 'Brainstorm Force',
						'time_to_display'     => '+24 hours',
						'deactivation_survey' => array(
							array(
								'id'                => 'deactivation-survey-bb-ultimate-addon',
								'popup_logo'        => BB_ULTIMATE_ADDON_URL . 'assets/images/uabb.svg',
								'plugin_slug'       => 'bb-ultimate-addon',
								'plugin_version'    => BB_ULTIMATE_ADDON_VER,
								'popup_title'       => 'Quick Feedback',
								'support_url'       => 'https://www.ultimatebeaver.com/contact/',
								'popup_description' => 'If you have a moment, please share why you are deactivating Ultimate Addons for Beaver Builder:',
								'show_on_screens'   => array( 'plugins' ),
							),
						),
						'hide_optin_checkbox' => true,
					),
				)
			);

			add_filter( 'bsf_core_stats', array( $this, 'add_uabb_analytics_data' ) );

			// State-event detection (admin-only; throttled via transient).
			add_action( 'admin_init', array( $this, 'detect_state_events' ) );

			// Activation event: first time a BB post containing a Pro-only UABB module is published.
			add_action( 'save_post', array( $this, 'maybe_track_first_pro_module_published' ), 20, 2 );

			// License lifecycle events — BSF core fires these on successful API round-trip.
			add_action( 'bsf_activate_license_uabb_after_success', array( $this, 'track_license_activated' ), 10, 0 );
			add_action( 'bsf_deactivate_license_uabb_after_success', array( $this, 'track_license_deactivated' ), 10, 0 );
		}

		/**
		 * Lazy-load the BSF_Analytics_Events instance once the library class is available.
		 *
		 * @since x.x.x
		 * @return BSF_Analytics_Events|null
		 */
		private static function events() {
			if ( null === self::$events && class_exists( 'BSF_Analytics_Events' ) ) {
				self::$events = new BSF_Analytics_Events( 'uabb' );
			}
			return self::$events;
		}

		/**
		 * Detect one-time milestone events on every admin page load.
		 *
		 * Throttled via a transient. The transient is set AFTER the null-check on
		 * the Events instance so that retries still happen until the library is loaded.
		 *
		 * @since x.x.x
		 * @return void
		 */
		public function detect_state_events() {
			if ( false !== get_transient( 'uabbpro_analytics_state_check' ) ) {
				return;
			}

			$events = self::events();
			if ( null === $events ) {
				return;
			}

			set_transient( 'uabbpro_analytics_state_check', '1', HOUR_IN_SECONDS );

			$this->detect_plugin_activated( $events );
			$this->detect_plugin_updated( $events );
			$this->detect_license_activated( $events );
		}

		/**
		 * Track `plugin_activated` once per site lifetime.
		 *
		 * @since x.x.x
		 * @param BSF_Analytics_Events $events Events instance.
		 * @return void
		 */
		private function detect_plugin_activated( $events ) {
			if ( $events->is_tracked( 'plugin_activated' ) ) {
				return;
			}

			$events->track(
				'plugin_activated',
				BB_ULTIMATE_ADDON_VER,
				array(
					'days_since_install'     => self::days_since_install(),
					'beaver_builder_active'  => defined( 'FL_BUILDER_VERSION' ),
					'beaver_builder_edition' => self::get_beaver_builder_edition(),
					'is_multisite'           => is_multisite(),
					'plan'                   => 'pro',
					'source'                 => self::get_install_source( 'bb-ultimate-addon' ),
				)
			);
		}

		/**
		 * Resolve the install source for the activation event.
		 *
		 * Reads the BSF UTM referer table written by other BSF products that installed
		 * us via TGM/PluginActivator. Returns the referer slug (e.g. `astra`,
		 * `cartflows`) when present, or `direct` when the user installed us directly.
		 *
		 * @since x.x.x
		 * @param string $product_slug This product's slug as recorded in `bsf_product_referers`.
		 * @return string
		 */
		private static function get_install_source( $product_slug ) {
			$referers = get_option( 'bsf_product_referers', array() );
			if ( ! is_array( $referers ) || empty( $referers[ $product_slug ] ) ) {
				return 'direct';
			}
			return (string) $referers[ $product_slug ];
		}

		/**
		 * Track `plugin_updated` on each Pro version bump.
		 *
		 * Uses a Pro-specific option (`uabbpro_analytics_last_tracked_version`) so the
		 * event fires exactly once per upgrade. A fresh install does not fire this event
		 * — only the first observed version-delta afterwards does.
		 *
		 * @since x.x.x
		 * @param BSF_Analytics_Events $events Events instance.
		 * @return void
		 */
		private function detect_plugin_updated( $events ) {
			$last_tracked = (string) get_option( 'uabbpro_analytics_last_tracked_version', '' );

			if ( BB_ULTIMATE_ADDON_VER === $last_tracked ) {
				return;
			}

			if ( '' === $last_tracked ) {
				update_option( 'uabbpro_analytics_last_tracked_version', BB_ULTIMATE_ADDON_VER );
				return;
			}

			$events->flush_pushed( array( 'plugin_updated' ) );
			$events->track(
				'plugin_updated',
				BB_ULTIMATE_ADDON_VER,
				array(
					'previous_version'   => $last_tracked,
					'new_version'        => BB_ULTIMATE_ADDON_VER,
					'days_since_install' => self::days_since_install(),
					'plan'               => 'pro',
				)
			);

			update_option( 'uabbpro_analytics_last_tracked_version', BB_ULTIMATE_ADDON_VER );
		}

		/**
		 * State-detection fallback for `license_activated`.
		 *
		 * The canonical path is the `bsf_activate_license_uabb_after_success` hook.
		 * This fallback catches sites where the license was already active before this
		 * code shipped — `bsf_is_active_license('uabb')` returning true without the
		 * event being pushed means a pre-existing licensed site. Fires once per site
		 * lifetime. Payload shape matches UAE's `license_activated` for cross-product
		 * query consistency.
		 *
		 * @since x.x.x
		 * @param BSF_Analytics_Events $events Events instance.
		 * @return void
		 */
		private function detect_license_activated( $events ) {
			if ( $events->is_tracked( 'license_activated' ) ) {
				return;
			}
			if ( ! class_exists( 'BSF_License_Manager' ) ) {
				return;
			}
			if ( ! BSF_License_Manager::bsf_is_active_license( 'uabb' ) ) {
				return;
			}

			$events->track(
				'license_activated',
				'yes',
				array( 'days_since_install' => (string) self::days_since_install() )
			);
		}

		/**
		 * Track `license_activated` on a successful BSF license activation API round-trip.
		 * Payload shape matches UAE — event value `'yes'` and a single `days_since_install`
		 * property (string). Fires once per site lifetime; library dedup ensures
		 * re-activations after a prior deactivation are not re-sent.
		 *
		 * @since x.x.x
		 * @return void
		 */
		public function track_license_activated() {
			$events = self::events();
			if ( null === $events || $events->is_tracked( 'license_activated' ) ) {
				return;
			}

			$events->track(
				'license_activated',
				'yes',
				array( 'days_since_install' => (string) self::days_since_install() )
			);
		}

		/**
		 * Track `license_deactivated` on a successful BSF license deactivation API
		 * round-trip. Payload shape matches UAE. Fires once per site lifetime — signals
		 * intent to churn or migrate.
		 *
		 * @since x.x.x
		 * @return void
		 */
		public function track_license_deactivated() {
			$events = self::events();
			if ( null === $events || $events->is_tracked( 'license_deactivated' ) ) {
				return;
			}

			$events->track(
				'license_deactivated',
				'yes',
				array( 'days_since_install' => (string) self::days_since_install() )
			);
		}

		/**
		 * Fire `first_uabb_pro_module_published` the first time a published BB post
		 * containing a UABB module is saved while the Pro plugin is active. Once the
		 * flag option `_uabbpro_first_module_published` is set, subsequent saves skip
		 * the meta scan.
		 *
		 * Note: Lite and Pro cannot run together (same class name, guarded by
		 * `class_exists`). Whenever this method runs, Pro is the active plugin, so
		 * no Lite-vs-Pro module filtering is needed — any UABB module publish counts
		 * as a Pro usage.
		 *
		 * @since x.x.x
		 * @param int     $post_id Post ID.
		 * @param WP_Post $post    Post object.
		 * @return void
		 */
		public function maybe_track_first_pro_module_published( $post_id, $post ) {
			if ( get_option( '_uabbpro_first_module_published', false ) ) {
				return;
			}
			if ( ! ( $post instanceof WP_Post ) || 'publish' !== $post->post_status ) {
				return;
			}
			if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
				return;
			}
			if ( '1' !== (string) get_post_meta( $post_id, '_fl_builder_enabled', true ) ) {
				return;
			}

			$events = self::events();
			if ( null === $events || $events->is_tracked( 'first_uabb_pro_module_published' ) ) {
				return;
			}

			$meta = get_post_meta( $post_id, '_fl_builder_data', true );
			if ( ! is_array( $meta ) && ! is_object( $meta ) ) {
				return;
			}

			$first_module = '';
			foreach ( (array) $meta as $node ) {
				if ( ! isset( $node->type ) || 'module' !== $node->type ) {
					continue;
				}
				if ( ! isset( $node->settings->type ) ) {
					continue;
				}
				$module_type = (string) $node->settings->type;
				if ( self::is_uabb_module( $module_type ) ) {
					$first_module = $module_type;
					break;
				}
			}

			if ( '' === $first_module ) {
				return;
			}

			update_option( '_uabbpro_first_module_published', 1 );

			$events->track(
				'first_uabb_pro_module_published',
				$first_module,
				array(
					'days_since_install' => self::days_since_install(),
					'module_slug'        => $first_module,
				)
			);
		}

		/**
		 * Check whether a BB module slug belongs to UABB. Mirrors the check used by
		 * UABBBuilderAdminSettings::is_uabb_module() (which is private).
		 *
		 * @since x.x.x
		 * @param string $module_type BB module slug from `_fl_builder_data` nodes.
		 * @return bool
		 */
		private static function is_uabb_module( $module_type ) {
			if ( ! class_exists( 'BB_Ultimate_Addon_Helper' ) ) {
				return false;
			}

			$all_modules = BB_Ultimate_Addon_Helper::get_all_modules();
			if ( ! is_array( $all_modules ) ) {
				return false;
			}

			foreach ( $all_modules as $pattern => $module_data ) {
				if ( 0 === strpos( $module_type, (string) $pattern ) ) {
					return true;
				}
			}

			return false;
		}

		/**
		 * Callback function to add specific analytics data.
		 *
		 * `events_record` flushes on every send (library per-event-name dedup).
		 * `numeric_values` + `kpi_records` ship on every send using whatever the
		 * weekly cron last wrote — ClickHouse dedupes on `(date, kpi_name)`, so
		 * re-shipping the same week's snapshot across 24h sends is a no-op
		 * server-side while keeping KPI delivery resilient to transport failures
		 * or missed sends without paying the weekly scan cost more than once.
		 *
		 * @since 1.36.16
		 * @param array $stats_data Existing stats data.
		 * @return array
		 */
		public function add_uabb_analytics_data( $stats_data ) {

			$stats_data['plugin_data']['uabb'] = array(
				'pro_version'            => BB_ULTIMATE_ADDON_VER,
				'site_language'          => get_locale(),
				'beaverbuilder_version'  => defined( 'FL_BUILDER_VERSION' ) ? FL_BUILDER_VERSION : '',
				'beaver_builder_edition' => self::get_beaver_builder_edition(),
			);

			$events = self::events();
			if ( null !== $events ) {
				$stats_data['plugin_data']['uabb']['events_record'] = $events->flush_pending();
			}

			$fetch_uabb_data = $this->uabb_get_module_usage();
			if ( ! empty( $fetch_uabb_data ) ) {
				foreach ( $fetch_uabb_data as $key => $value ) {
					$stats_data['plugin_data']['uabb']['numeric_values'][ $key ] = $value;
				}
			}

			$kpi_records = $this->get_kpi_tracking_data();
			if ( ! empty( $kpi_records ) ) {
				$stats_data['plugin_data']['uabb']['kpi_records'] = $kpi_records;
			}

			return $stats_data;
		}

		/**
		 * Build the KPI payload from the latest snapshot written by the weekly Pro
		 * module-usage cron.
		 *
		 * The writer (`save_kpi_snapshot()`) already persists each day in the BSF
		 * analytics ingestion contract shape, so the reader returns it verbatim.
		 *
		 * Shape: `[ 'Y-m-d' => [ 'numeric_values' => [ kpi_name => kpi_value ] ] ]`.
		 *
		 * @since x.x.x
		 * @return array<string, array{numeric_values: array<string, int|float>}>
		 */
		private function get_kpi_tracking_data() {
			$snapshots = get_option( 'uabbpro_kpi_daily_snapshots', array() );
			if ( ! is_array( $snapshots ) || empty( $snapshots ) ) {
				return array();
			}

			$records = array();
			foreach ( $snapshots as $date => $snapshot ) {
				if ( ! is_array( $snapshot ) || empty( $snapshot['numeric_values'] ) ) {
					continue;
				}
				$records[ (string) $date ] = $snapshot;
			}

			return $records;
		}

		/**
		 * Detect Beaver Builder edition.
		 *
		 * The paid edition ships `FLUpdater` for license-aware updates; Lite does not.
		 * Standard / Pro / Agency cannot be distinguished from class presence on a
		 * single site, so those are grouped as `paid`.
		 *
		 * @since x.x.x
		 * @return string One of: `none`, `lite`, `paid`.
		 */
		private static function get_beaver_builder_edition() {
			if ( ! defined( 'FL_BUILDER_VERSION' ) ) {
				return 'none';
			}

			if ( class_exists( 'FLUpdater' ) ) {
				return 'paid';
			}

			return 'lite';
		}

		/**
		 * Days elapsed since the site first opted into analytics.
		 *
		 * The BSF Analytics library persists this via `update_site_option`, but the legacy
		 * opt-in migration in `UABB_Init::uabb_maybe_migrate_analytics_tracking()` writes
		 * the same key via `update_option`. On multisite those target different stores,
		 * so both are checked.
		 *
		 * @since x.x.x
		 * @return int
		 */
		private static function days_since_install() {
			$installed = (int) get_site_option( 'uabb_usage_installed_time', 0 );
			if ( 0 === $installed ) {
				$installed = (int) get_option( 'uabb_usage_installed_time', 0 );
			}
			if ( 0 === $installed ) {
				return 0;
			}
			return (int) floor( ( time() - $installed ) / DAY_IN_SECONDS );
		}

		/**
		 * Fetch Pro module usage data (written weekly by `uabbpro_module_usage_cron`).
		 *
		 * @since 1.36.16
		 * @return array
		 */
		private function uabb_get_module_usage() {
			return get_option( 'uabbpro_module_usage_data_option', array() );
		}
	}
}

new UABB_Analytics();
