import {useState, useEffect, useCallback, createContext, useContext, useRef, useMemo} from '@wordpress/element';
import {Spinner} from '@wordpress/components';
import ChartRenderer from './ChartRenderer';
import {addQueryArgs, getQueryArgs} from '@wordpress/url';
import FilterBar from './FilterBar';
import DataTable from './DataTable';
import './StatisticsApp.scss';
import {__} from '@wordpress/i18n';
import DrilldownStatus from './DrilldownStatus';

import useProDataFetching from '../../../../pro/admin/assets/tracking/src/useProDataFetching';
import AppHeader from '../../../../admin/assets/header/src/AppHeader';
import TokenSelectField from '../../../../admin/assets/design/form-fields/TokenSelectField';
import MetricToggles from './MetricToggles';
import Button from '../../../../admin/assets/design/components/Button';
import CloseIcon from '../../../../admin/assets/design/icons/CloseIcon';
import TabNav from '../../../../admin/assets/design/components/TabNav';

const proComponents = window.adpresso?.proComponents || [];
const AnalyticsContext = createContext( null );
export const useAnalyticsContext = () => useContext( AnalyticsContext );
if ( typeof window !== 'undefined' ) {
	window.adpresso = window.adpresso || {};
	window.adpresso.useAnalyticsContext = useAnalyticsContext;
}
const rawData = window.adpressoAnalyticsData || {};
const isPro = rawData.isPro === '1';
const {restUrl, apiNonce, defaultView, logoUrl} = rawData;
const {applyFilters} = (window.adpresso && window.adpresso.hooks) || {applyFilters: ( name, val ) => val};

/**
 * Compares to flat objects with each other.
 *
 * @param {object} objA
 * @param {object} objB
 * @returns {boolean}
 */
const areObjectsEqual = ( objA, objB ) => {
	// Check the case, that one of the objects is null (e.g. no default view set)
	if ( objA === null || objB === null ) {
		return objA === objB;
	}

	const keysA = Object.keys( objA );
	const keysB = Object.keys( objB );

	// They need to have the same number of keys.
	if ( keysA.length !== keysB.length ) {
		return false;
	}

	// They need to have the same values for their keys.
	for ( const key of keysA ) {
		if ( objA[key] !== objB[key] ) {
			return false;
		}
	}

	return true;
};

const buildCurrentParams = ( dateRange, grouping, viewMode, sorting, filters ) => {
	const currentParams = {
		range:             dateRange,
		group_by:          grouping,
		view_mode:         viewMode,
		orderby:           sorting.orderBy,
		order:             sorting.order,
		filter_ads:        filters.ads.join( ',' ),
		filter_groups:     filters.groups.join( ',' ),
		filter_placements: filters.placements.join( ',' )
	};

	// Remove empty parameters for clean comparing.
	Object.keys( currentParams ).forEach( key => {
		if ( !currentParams[key] ) {
			delete currentParams[key];
		}
	} );
	return currentParams;
};

const getInitialStateFromURL = () => {
	const urlParams = getQueryArgs( window.location.search );

	const parseArrayParam = ( param ) => (param ? param.split( ',' ) : []);

	let initRange = urlParams.range;
	if ( typeof initRange === 'object' ) {
		initRange = 'last_30_days';
	}

	return {
		dateRange: initRange || 'last_30_days',
		grouping:  urlParams.group_by || 'day',
		viewMode:  urlParams.view_mode || 'date',
		sorting:   {
			orderBy: urlParams.orderby || 'impressions',
			order:   urlParams.order || 'desc'
		},
		filters:   {
			ads:        parseArrayParam( urlParams.filter_ads ),
			groups:     parseArrayParam( urlParams.filter_groups ),
			placements: parseArrayParam( urlParams.filter_placements )
		}
		// visibleMetrics is local for the moment, but could be added here too.
	};
};

const StatisticsApp = () => {
	const [previousState, setPreviousState] = useState( null );
	const [chartData, setChartData] = useState( [] );
	const [tableData, setTableData] = useState( [] );
	const [compareData, setCompareData] = useState( null );
	const [compareMode, setCompareMode] = useState( null );
	const [isChartLoading, setIsChartLoading] = useState( true );
	const [isTableLoading, setIsTableLoading] = useState( true );
	const [customStartDate, setCustomStartDate] = useState( null );
	const [customEndDate, setCustomEndDate] = useState( null );
	const [visibleMetrics, setVisibleMetrics] = useState( ['impressions', 'clicks'] );
	const [filterOptions, setFilterOptions] = useState( {ads: [], groups: [], placements: []} );
	const initialState = getInitialStateFromURL();
	const [dateRange, setDateRange] = useState( initialState.dateRange );
	const [grouping, setGrouping] = useState( initialState.grouping );
	const [viewMode, setViewMode] = useState( initialState.viewMode );
	const [sorting, setSorting] = useState( initialState.sorting );
	const [filters, setFilters] = useState( initialState.filters );
	const [savedDefaultView, setSavedDefaultView] = useState( defaultView || null );

	const contextValue = useMemo( () => ({
		dateRange,
		customStartDate,
		customEndDate,
		filters,
		grouping,
		viewMode,
		compareMode,
		setCompareMode,
		apiNonce,
		restUrl
	}), [dateRange, customStartDate, customEndDate, filters, grouping, viewMode, compareMode, apiNonce, restUrl] );
	const chartPayload = {dateRange, filters, grouping, setChartData, setCompareData, setIsChartLoading, customStartDate, customEndDate, ...contextValue};
	const tablePayload = {dateRange, viewMode, filters, grouping, sorting, setTableData, setCompareData, setIsTableLoading, customStartDate, customEndDate, ...contextValue};

	// --- STICKY OBSERVER LOGIC ---
	const sentinelRef = useRef( null );
	const filterBarRef = useRef( null );
	const filtersRef = useRef( filters );
	const isScrolledRef = useRef( false );

	const updateStickyState = () => {
		const bar = filterBarRef.current;
		if ( !bar ) {
			return;
		}

		const currentFilters = filtersRef.current;

		// Check: do we have active filters.
		const hasActiveFilters =
			(currentFilters.ads && currentFilters.ads.length > 0) ||
			(currentFilters.groups && currentFilters.groups.length > 0) ||
			(currentFilters.placements && currentFilters.placements.length > 0);

		// Only sticky when scrolled AND filters are active.
		if ( isScrolledRef.current && hasActiveFilters ) {
			bar.classList.add( 'is-stuck' );
		} else {
			bar.classList.remove( 'is-stuck' );
		}
	};

	const availableColumns = [
		{key: 'impressions', label: __( 'Rendered Impressions', 'adpresso' )},
		{key: 'clicks', label: __( 'Clicks', 'adpresso' )},
		{key: 'ctr', label: __( 'CTR (%)', 'adpresso' )},
		{key: 'viewability', label: __( 'Active View Rate (%)', 'adpresso' ), disabled: !isPro},
		{key: 'impressions_av', label: __( 'Active View Impressions', 'adpresso' ), disabled: !isPro}
	];

	// 2. Initial State: Set everything to true.
	const [visibleColumns, setVisibleColumns] = useState( {
		impressions:    true,
		impressions_av: false,
		clicks:         true,
		ctr:            true,
		viewability:    isPro
	} );

	// Check sticky state.
	useEffect( () => {
		filtersRef.current = filters;
		updateStickyState();
	}, [filters] );

	useEffect( () => {
		const sentinelEl = sentinelRef.current;
		const filterBarEl = filterBarRef.current;

		if ( !sentinelEl || !filterBarEl ) {
			return;
		}

		const observer = new IntersectionObserver(
			( entries ) => {
				const entry = entries[0];

				// !isIntersecting means: Sentinel has been scrolled out.
				// Save the scroll status in the ref.
				isScrolledRef.current = !entry.isIntersecting;

				// Call the central logic.
				updateStickyState();
			},
			{
				root:       null,
				threshold:  0,
				rootMargin: '-33px 0px 0px 0px'
			}
		);

		observer.observe( sentinelEl );

		return () => observer.disconnect();

	}, [isChartLoading, isTableLoading] );

	if ( isPro ) {
		useProDataFetching( chartPayload, tablePayload );
	}

	// Effect to write the state back in the URL.
	useEffect( () => {
		const currentUrlParams = getQueryArgs( window.location.search );

		// Create the query object.
		const queryArgs = {
			page:      currentUrlParams.page || 'adpresso-statistics',
			range:     dateRange,
			group_by:  grouping,
			view_mode: viewMode,
			orderby:   sorting.orderBy,
			order:     sorting.order,

			// Add filters when they aren't empty.
			filter_ads:        filters.ads.length ? filters.ads.join( ',' ) : undefined,
			filter_groups:     filters.groups.length ? filters.groups.join( ',' ) : undefined,
			filter_placements: filters.placements.length ? filters.placements.join( ',' ) : undefined
		};

		// Create the new URL.
		const newUrl = addQueryArgs( window.location.pathname, queryArgs );
		window.history.pushState( {path: newUrl}, '', newUrl );

	}, [dateRange, grouping, viewMode, sorting, filters] );

	useEffect( () => {
		const endpoint = `adpresso/v1/analytics/filter-options`;
		wp.apiFetch( {path: endpoint, headers: {'X-WP-Nonce': apiNonce}} )
		  .then( data => setFilterOptions( data ) )
		  .catch( error => console.error( 'Failed to load filter options:', error ) );
	}, [] );

	// Loading effect for the chart.
	useEffect( () => {
		const defaultChartLogic = () => {
			setIsChartLoading( true );

			let queryArgs = {
				range:     dateRange,
				view_mode: 'date',
				group_by:  grouping,
				orderby:   'date',
				order:     'asc'
			};

			if ( compareMode ) {
				queryArgs.compare = compareMode;
			}

			// Add Custom Dates
			if ( dateRange === 'custom' && customStartDate && customEndDate ) {
				queryArgs.start_date = customStartDate;
				queryArgs.end_date = customEndDate;
			}

			if ( filters.ads.length > 0 ) {
				queryArgs['filter_ads'] = filters.ads.join( ',' );
			}
			if ( filters.groups.length > 0 ) {
				queryArgs['filter_groups'] = filters.groups.join( ',' );
			}
			if ( filters.placements.length > 0 ) {
				queryArgs['filter_placements'] = filters.placements.join( ',' );
			}

			const endpoint = addQueryArgs( `adpresso/v1/analytics/overview`, queryArgs );
			wp.apiFetch( {path: endpoint, headers: {'X-WP-Nonce': apiNonce}} )
			  .then( data => {
				  setChartData( data || data.main );
				  if ( data.compare ) {
					  setCompareData( data.compare );
				  }
			  } )
			  .catch( error => {
				  console.error( 'Failed to load analytics data:', error );
				  setChartData( [] );
			  } )
			  .finally( () => {
				  setIsChartLoading( false );
			  } );
		};

		const proFilterPayload = {
			dateRange, filters, grouping, setChartData, setCompareData, setIsChartLoading,
			apiNonce, restUrl
		};
		const logicToRun = applyFilters( 'adpresso.analytics.fetchChartData', defaultChartLogic, proFilterPayload );
		if ( typeof logicToRun === 'function' ) {
			logicToRun();
		}

	}, [dateRange, filters, grouping, customStartDate, customEndDate] );

	// Data hook for the table.
	useEffect( () => {
		const defaultTableLogic = () => {
			setIsTableLoading( true );
			let queryArgs = {
				range:     dateRange,
				view_mode: viewMode,
				group_by:  grouping,
				orderby:   sorting.orderBy,
				order:     sorting.order
			};

			if ( dateRange === 'custom' && customStartDate && customEndDate ) {
				queryArgs.start_date = customStartDate;
				queryArgs.end_date = customEndDate;
			}

			if ( filters.ads.length > 0 ) {
				queryArgs['filter_ads'] = filters.ads.join( ',' );
			}
			if ( filters.groups.length > 0 ) {
				queryArgs['filter_groups'] = filters.groups.join( ',' );
			}
			if ( filters.placements.length > 0 ) {
				queryArgs['filter_placements'] = filters.placements.join( ',' );
			}

			const endpoint = addQueryArgs( `adpresso/v1/analytics/overview`, queryArgs );
			wp.apiFetch( {path: endpoint, headers: {'X-WP-Nonce': apiNonce}} )
			  .then( data => setTableData( data ) )
			  .catch( error => setTableData( [] ) )
			  .finally( () => setIsTableLoading( false ) );
		};

		const proFilterPayload = {
			dateRange, viewMode, filters, grouping, sorting,
			setTableData, setCompareData, setIsTableLoading,
			apiNonce, restUrl
		};

		const logicToRun = applyFilters( 'adpresso.analytics.fetchTableData', defaultTableLogic, proFilterPayload );
		if ( typeof logicToRun === 'function' ) {
			logicToRun();
		}

	}, [dateRange, viewMode, filters, sorting, customStartDate, customEndDate, grouping] );

	const handleSaveView = useCallback( () => {
		const currentParams = buildCurrentParams( dateRange, grouping, viewMode, sorting, filters );

		// Check if the current state is already the saved one.
		const isCurrentlyDefault = areObjectsEqual( currentParams, savedDefaultView );

		let payload = {};

		// Set the current view, if it's not the saved one.
		if ( !isCurrentlyDefault ) {
			payload = currentParams;
		}

		wp.apiFetch( {
			path:    `adpresso/v1/analytics/save-default-view`,
			method:  'POST',
			headers: {'X-WP-Nonce': apiNonce},
			data:    {'view_params': payload}
		} )
		  .then( response => {
			  console.log( 'Response:', response );
			  if ( response.action === 'saved' ) {
				  setSavedDefaultView( response.saved_view );
				  if ( window.AdPresso?.notifications ) {
					  window.AdPresso.notifications.addSuccess( __( 'Default view saved!', 'adpresso' ) );
				  }
			  } else if ( response.action === 'deleted' ) {
				  setSavedDefaultView( null );
				  if ( window.AdPresso?.notifications ) {
					  window.AdPresso.notifications.addInfo( __( 'Default view removed.', 'adpresso' ) );
				  }
			  }
		  } )
		  .catch( error => {
			  console.error( 'Failed to save view:', error );
			  if ( window.AdPresso?.notifications ) {
				  window.AdPresso.notifications.addError( __( 'Could not save default view. Please try again.', 'adpresso' ) );
			  }
		  } );

	}, [dateRange, grouping, viewMode, sorting, filters, apiNonce, savedDefaultView] );

	const handleDrilldown = ( drilldownParams ) => {
		setPreviousState( {
			viewMode: viewMode,
			filters:  filters
		} );

		setViewMode( drilldownParams.viewMode );
		setFilters( prevFilters => ({
			...prevFilters,
			...drilldownParams.filters
		}) );
	};

	const handleGoBack = () => {
		if ( !previousState ) {
			return;
		}

		setViewMode( previousState.viewMode );
		setFilters( previousState.filters );

		setPreviousState( null );
	};

	const handleRangeChange = ( newRange ) => {

		if ( typeof newRange === 'string' ) {
			// Case A: Preset (e.g. 'last_30_days')
			setDateRange( newRange );
			setCustomStartDate( null );
			setCustomEndDate( null );
		} else if ( typeof newRange === 'object' && newRange !== null ) {
			// Case B: Custom Range Object from the calendar.
			setDateRange( 'custom' );
			setCustomStartDate( newRange.start );
			setCustomEndDate( newRange.end );
		}
	};

	const viewModeOptions = [
		{key: 'date', label: __( 'List by Date', 'adpresso' )},
		{key: 'ads', label: __( 'By Ad', 'adpresso' )},
		{key: 'groups', label: __( 'By Group', 'adpresso' )},
		{key: 'placements', label: __( 'By Placement', 'adpresso' )}
	];

	const currentParams = buildCurrentParams( dateRange, grouping, viewMode, sorting, filters );
	const isDefaultView = areObjectsEqual( currentParams, savedDefaultView );
	const adFilterField = useMemo( () => ({
		id:          'ads',
		placeholder: __( 'Filter by Ads...', 'adpresso' ),
		options:     filterOptions.ads
	}), [filterOptions.ads] );
	const groupFilterField = useMemo( () => ({
		id:          'groups',
		placeholder: __( 'Filter by Groups...', 'adpresso' ),
		options:     filterOptions.groups
	}), [filterOptions.groups] );
	const placementFilterField = useMemo( () => ({
		id:          'placements',
		placeholder: __( 'Filter by Placements...', 'adpresso' ),
		options:     filterOptions.placements
	}), [filterOptions.placements] );

	const handleFilterChange = ( filterKey, newValues ) => {
		setFilters( prevFilters => ({
			...prevFilters,
			[filterKey]: newValues
		}) );
	};

	const hasActiveFilters =
		(filters.ads && filters.ads.length > 0) ||
		(filters.groups && filters.groups.length > 0) ||
		(filters.placements && filters.placements.length > 0);

	// The reset function.
	const handleResetFilters = () => {
		const resetState = {
			...filters,
			ads:        [],
			groups:     [],
			placements: []
		};

		if ( typeof setFilters === 'function' ) {
			setFilters( resetState );
		}
	};

	if ( isChartLoading && isTableLoading && tableData.length === 0 ) {
		return <Spinner/>;
	}

	const rangeParamForView = (dateRange === 'custom')
		? {key: 'custom', start: customStartDate, end: customEndDate}
		: dateRange;

	return (
		<div className="adpresso-app-container">
			<AnalyticsContext.Provider value={contextValue}>
				<AppHeader
					allColumns={availableColumns}
					visibleColumns={visibleColumns}
					setVisibleColumns={setVisibleColumns}
					logoUrl={logoUrl}
					screenOptionsLabel={__( 'Show table columns', 'adpresso' )}
				/>
				<h2 className="hidden">AdPresso</h2>
				<nav className="adpresso-tab-navigation adpresso-settings-nav">
					<div className="adpresso-tab-item adpresso-settings-arrow">
						<a href="/wp-admin/admin.php?page=adpresso">
							<svg width="26" height="26" viewBox="0 0 26 26" fill="none" stroke="currentColor">
								<path d="M16.5 3L6.5 13L16.5 23" strokeWidth="4" strokeLinecap="square" strokeLinejoin="miter"></path>
							</svg>
						</a>
					</div>
					<div className="adpresso-tab-item adpresso-settings-heading">
							<span className="adpresso-tab-label">
								{__( 'Statistics', 'adpresso' )}
							</span>
					</div>
					<FilterBar
						currentRange={rangeParamForView}
						onRangeChange={handleRangeChange}
						currentViewMode={viewMode}
						onViewModeChange={setViewMode}
						filterOptions={filterOptions}
						currentFilters={filters}
						onFiltersChange={setFilters}
						currentGrouping={grouping}
						onGroupingChange={setGrouping}
						visibleMetrics={visibleMetrics}
						onMetricsChange={setVisibleMetrics}
						proComponents={proComponents}
						onSaveView={handleSaveView}
						isDefaultView={isDefaultView}
					/>
				</nav>
				<div ref={sentinelRef} style={{height: '1px', marginTop: '-1px', visibility: 'hidden'}}/>
				<div className="adpresso-analytics-entity-filters" ref={filterBarRef}>
					<TokenSelectField
						key={filterOptions.ads.length > 0 ? 'ads-loaded' : 'ads-loading'}
						field={adFilterField}
						value={filters.ads}
						onChange={handleFilterChange}
						tokenClassName="adpresso-color-ad"
					/>
					<TokenSelectField
						key={filterOptions.groups.length > 0 ? 'groups-loaded' : 'groups-loading'}
						field={groupFilterField}
						value={filters.groups}
						onChange={handleFilterChange}
						tokenClassName="adpresso-color-group"
					/>
					<TokenSelectField
						key={filterOptions.placements.length > 0 ? 'placements-loaded' : 'placements-loading'}
						field={placementFilterField}
						value={filters.placements}
						onChange={handleFilterChange}
						tokenClassName="adpresso-color-placement"
					/>
					<Button variant="destructive"
					        className="adpresso-btn adpresso-close "
					        icon={<CloseIcon width="8" height="8"/>}
					        onClick={handleResetFilters}
					        title={__( 'Reset filters', 'adpresso' )}
					        disabled={!hasActiveFilters}
					/>
				</div>
				<div className="adpresso-analytics-dashboard">
					<div className="adpresso-analytics-widget-container">
						<div className="adpresso-analytics-chart-container">
							<MetricToggles
								visibleMetrics={visibleMetrics}
								onMetricsChange={setVisibleMetrics}
							/>
							{isChartLoading && (
								<div className="adpresso-analytics-overlay">
									<Spinner/>
								</div>
							)}
							{chartData.length > 0 ? (
								<ChartRenderer chartData={chartData} visibleMetrics={visibleMetrics} compareData={compareData}/>
							) : (
								!isChartLoading && <p>No data found for this period.</p>
							)}
						</div>
						<div className="adpresso-analytics-table-container">
							<TabNav
								options={viewModeOptions}
								value={viewMode}
								onChange={setViewMode}
							/>
							{/*<DrilldownStatus*/}
							{/*	filters={filters}*/}
							{/*	filterOptions={filterOptions}*/}
							{/*	onFiltersChange={setFilters}*/}
							{/*	previousState={previousState}*/}
							{/*	onGoBack={handleGoBack}*/}
							{/*/>*/}
							{isTableLoading && (
								<div className="adpresso-analytics-overlay">
									<Spinner/>
								</div>
							)}
							<DataTable
								tableData={tableData}
								compareData={compareData}
								viewMode={viewMode}
								sorting={sorting}
								onSort={setSorting}
								onDrilldown={handleDrilldown}
								visibleColumns={visibleColumns}
								isPro={isPro}
							/>
						</div>
					</div>
				</div>
			</AnalyticsContext.Provider>
		</div>
	);
};

export default StatisticsApp;
