/**
 * External dependencies
 */
import debugFactory from 'debug';
/**
 * Internal dependencies
 */
import { ApiClient } from './api-client';
import { consentManager } from './consent';
import { EVENT_NAME_REGEX, EVENT_PREFIX } from './constants';
import SessionManager from './session-manager';
import { getCookie, generateRandomToken } from './utils';
import type { AnalyticsConfig } from './types/shared';

const debug = debugFactory( 'wc-analytics:analytics' );

/**
 * Analytics class for WooCommerce Analytics.
 */
export class Analytics {
	private isInitialized: boolean;
	private sessionManager: SessionManager;
	private apiClient: ApiClient;
	private eventQueue: AnalyticsConfig[ 'eventQueue' ];
	private commonProps: AnalyticsConfig[ 'commonProps' ];
	private features: AnalyticsConfig[ 'features' ];
	private pages: AnalyticsConfig[ 'pages' ];

	constructor(
		sessionManager: SessionManager,
		{ eventQueue = [], commonProps = {}, features = {}, pages = {} }: AnalyticsConfig
	) {
		this.isInitialized = false;

		this.sessionManager = sessionManager;
		this.apiClient = new ApiClient();
		this.eventQueue = eventQueue;
		this.commonProps = commonProps;
		this.features = features;
		this.pages = pages;
	}

	/**
	 * Initialize the analytics.
	 */
	init = () => {
		if ( this.isInitialized ) {
			return;
		}

		// Initialize API client if proxy tracking is enabled
		if ( this.features.proxy ) {
			this.apiClient.init();
		}

		/*
		 * Initialize the session manager and record the page_view event
		 * only if the ClickHouse (ch) feature is enabled as these events are relevant exclusively when ClickHouse is active.
		 */
		if ( this.features.sessionTracking ) {
			// Set up consent change listener
			consentManager.addConsentChangeListener( this.handleConsentChange );

			this.sessionManager.init();
			const { sessionId, landingPage, isNewSession } = this.sessionManager;

			// Not needed if proxy tracking is enabled.
			if ( ! this.features.proxy ) {
				// Add session ID and landing page to common properties.
				this.commonProps = {
					...this.commonProps,
					session_id: sessionId,
					landing_page: landingPage,
				};
			}

			if ( isNewSession ) {
				this.maybeRecordSessionStartedEvent();
			} else {
				this.maybeRecordEngagementEvent();
			}

			this.recordEvent( 'page_view' );
		}

		this.maybeSetAnonId();
		this.processEventQueue();
		this.initListeners();

		this.isInitialized = true;
	};

	/**
	 * Initialize Listeners for pages.
	 */
	initListeners = (): void => {
		if ( this.pages.isAccountPage ) {
			import( './listeners/account' ).then( ( { initListeners } ) => {
				initListeners( this.recordEvent );
			} );
		}
	};

	/**
	 * Process the event queue.
	 */
	processEventQueue = (): void => {
		for ( const event of this.eventQueue ) {
			this.recordEvent( event.eventName, event.props );
		}
	};

	/**
	 * Record an event.
	 * @param event      - The name of the event.
	 * @param properties - The properties of the event.
	 */
	recordEvent = ( event: string, properties: Record< string, unknown > = {} ): void => {
		// Check consent before recording any event
		if ( ! consentManager.hasAnalyticsConsent() ) {
			debug( 'Skipping event recording due to lack of statistics consent: %s', event );
			return;
		}

		// Validate event name
		if ( typeof event !== 'string' || ! EVENT_NAME_REGEX.test( event ) ) {
			debug( 'Skipping event recording because event name is not valid' );
			return;
		}

		const eventProperties = {
			...this.commonProps,
			...properties,
		};
		// Use API client if enabled, otherwise fall back to _wca.push
		if ( this.features.proxy ) {
			// Add client specific properties to the event properties. We don't need to do this for direct pixel tracking since it's already done there.
			this.addClientProperties( eventProperties );
			this.apiClient.addEvent( event, eventProperties );
		} else {
			this.fireDirectPixel( event, eventProperties );
		}

		// Post initialization, maybe record engagement event.
		if ( this.isInitialized ) {
			this.maybeRecordEngagementEvent();
		}
	};

	/**
	 * Fire a pixel event.
	 * @param event           - The name of the event.
	 * @param eventProperties - The properties of the event.
	 */
	fireDirectPixel = ( event: string, eventProperties: Record< string, unknown > ): void => {
		// Legacy _wca tracking
		if ( ! window._wca ) {
			debug( 'Skipping event recording because _wca is not defined' );
			return;
		}

		if ( this.features.ch ) {
			eventProperties.ch = 1;
		} else {
			delete eventProperties.ch;
		}

		debug( 'Recording event via _wca: "%s" with props %o', event, eventProperties );

		eventProperties._en = `${ EVENT_PREFIX }${ event }`;
		window._wca.push( eventProperties );
	};

	/**
	 * Add client properties to the event properties.
	 * @param eventProperties - The properties of the event.
	 */
	addClientProperties = ( eventProperties: Record< string, unknown > ): void => {
		const date = new Date();
		eventProperties._ts = date.getTime();
		eventProperties._tz = date.getTimezoneOffset() / 60;

		const nav = window.navigator;
		const screen = window.screen;
		eventProperties._lg = nav.language;
		eventProperties._pf = navigator?.platform;
		eventProperties._ht = screen.height;
		eventProperties._wd = screen.width;

		const sx =
			window.pageXOffset !== undefined
				? window.pageXOffset
				: ( document.documentElement || document.body ).scrollLeft;
		const sy =
			window.pageYOffset !== undefined
				? window.pageYOffset
				: ( document.documentElement || document.body ).scrollTop;

		eventProperties._sx = sx !== undefined ? sx : 0;
		eventProperties._sy = sy !== undefined ? sy : 0;

		if ( document.location !== undefined ) {
			eventProperties._dl = document.location.toString();
		}
		if ( document.referrer !== undefined ) {
			eventProperties._dr = document.referrer;
		}
	};

	/**
	 * Record the session started event if it's a new session and session ID is set.
	 */
	maybeRecordSessionStartedEvent = (): void => {
		if ( ! this.features.sessionTracking ) {
			return;
		}

		if ( ! this.sessionManager.isNewSession || ! this.sessionManager.sessionId ) {
			return;
		}

		this.recordEvent( 'session_started' );
	};

	/**
	 * Record the session engagement event if session is not engaged and session ID is set.
	 */
	maybeRecordEngagementEvent = (): void => {
		if ( ! this.features.sessionTracking ) {
			return;
		}

		if ( this.sessionManager.isEngaged || ! this.sessionManager.sessionId ) {
			return;
		}

		this.sessionManager.setEngaged();
		this.recordEvent( 'session_engagement' );
	};

	/**
	 * Handle consent changes
	 *
	 * @param hasConsent - Whether the user has granted consent
	 */
	handleConsentChange = ( hasConsent: boolean ): void => {
		if ( ! hasConsent ) {
			// Consent withdrawn - clear session data if session tracking is enabled
			this.sessionManager.clearSession();
		} else if ( ! this.sessionManager.sessionId ) {
			// Consent granted - reinitialize session if needed
			this.sessionManager.init();
		}
	};

	/**
	 * Set anonymous ID if not already set
	 */
	private maybeSetAnonId() {
		const anonId = getCookie( 'tk_ai' );

		if ( ! anonId ) {
			// Set a first-party cookie (same domain only, 1 year)
			const randomToken = generateRandomToken( 18 ); // 18 * 4/3 = 24 (base64 encoded chars)
			const expires = new Date( Date.now() + 1 * 365 * 24 * 60 * 60 * 1000 ).toUTCString();
			document.cookie = `tk_ai=${ randomToken }; path=/; secure; samesite=strict; expires=${ expires }`;
		}
	}
}
