/**
 * Query builder state management hook.
 *
 * @package RawTypeOE
 */

import { useCallback, useEffect, useMemo, useRef, useState } from '@wordpress/element';
import apiFetch from '../utils/apiFetch';
import { applyFilters } from '@wordpress/hooks';
import {
	isValidCrossEntitySelection,
	sanitizeKey,
} from '../utils/crossEntity';

const WIZARD_STEPS = [ 'entity', 'filters', 'columns', 'export' ];

const COUNTS_SESSION_KEY = 'rawtype_oe_entity_counts_cache';
const COUNTS_SESSION_TTL_MS = 5 * 60 * 1000;

function readCachedCounts() {
	if ( 'undefined' === typeof window || ! window.sessionStorage ) {
		return null;
	}

	try {
		const raw = window.sessionStorage.getItem( COUNTS_SESSION_KEY );
		if ( ! raw ) {
			return null;
		}
		const parsed = JSON.parse( raw );
		if (
			parsed &&
			'object' === typeof parsed &&
			parsed.timestamp &&
			Date.now() - parsed.timestamp < COUNTS_SESSION_TTL_MS &&
			parsed.data
		) {
			return parsed.data;
		}
	} catch ( e ) {
		return null;
	}

	return null;
}

function writeCachedCounts( data ) {
	if ( 'undefined' === typeof window || ! window.sessionStorage ) {
		return;
	}

	try {
		window.sessionStorage.setItem(
			COUNTS_SESSION_KEY,
			JSON.stringify( { timestamp: Date.now(), data } ),
		);
	} catch ( e ) {
		// Quota exceeded — ignore.
	}
}

/**
 * Resolve a relative date string (from PHP templates) to an ISO date.
 *
 * Handles formats like "-30 days", "-90 days" and preset keywords like
 * "this_quarter", "this_month", "this_year", "today".
 */
function resolveRelativeDate( value ) {
	if ( 'string' !== typeof value ) {
		return value;
	}

	const daysMatch = value.match( /^-(\d+)\s*days?$/i );
	if ( daysMatch ) {
		const d = new Date();
		d.setDate( d.getDate() - parseInt( daysMatch[ 1 ], 10 ) );
		return d.toISOString().split( 'T' )[ 0 ];
	}

	const now = new Date();
	switch ( value ) {
		case 'today':
			return now.toISOString().split( 'T' )[ 0 ];
		case 'this_month': {
			const d = new Date( now.getFullYear(), now.getMonth(), 1 );
			return d.toISOString().split( 'T' )[ 0 ];
		}
		case 'this_quarter': {
			const quarter = Math.floor( now.getMonth() / 3 ) * 3;
			const d = new Date( now.getFullYear(), quarter, 1 );
			return d.toISOString().split( 'T' )[ 0 ];
		}
		case 'this_year':
			return now.getFullYear() + '-01-01';
		default:
			return value;
	}
}

const OPERATOR_MAP = {
	greater_than: '>',
	less_than: '<',
	equal_to: '=',
	greater_equal: '>=',
	less_equal: '<=',
};

/**
 * Normalize a filter from an external config (template/profile) so its values
 * match what the UI filter chip components expect.
 */
function normalizeConfigFilter( filter ) {
	if ( ! filter || 'object' !== typeof filter || ! filter.type ) {
		return null;
	}

	const normalized = { ...filter };

	// Date filters: resolve relative values to ISO dates.
	if ( 'date' === normalized.type ) {
		if ( 'string' === typeof normalized.value ) {
			normalized.value = resolveRelativeDate( normalized.value );
		} else if ( Array.isArray( normalized.value ) ) {
			normalized.value = normalized.value.map( resolveRelativeDate );
		}
	}

	// Numeric filters: map backend operator names to UI symbols.
	if ( OPERATOR_MAP[ normalized.operator ] ) {
		normalized.operator = OPERATOR_MAP[ normalized.operator ];
	}

	// Status filters: ensure value is a string (take first if array).
	if ( 'status' === normalized.type ) {
		if ( Array.isArray( normalized.value ) ) {
			normalized.value = normalized.value[ 0 ] || '';
		} else if ( 'string' !== typeof normalized.value ) {
			normalized.value = String( normalized.value || '' );
		}
	}

	return normalized;
}

function buildSelectedColumns( columns, presetKeys = null ) {
	if ( Array.isArray( presetKeys ) && presetKeys.length > 0 ) {
		return presetKeys
			.map( ( presetColumn, index ) => {
				const colKey =
					'string' === typeof presetColumn
						? presetColumn
						: presetColumn?.key;
				const definition = columns.find( ( col ) => col.key === colKey );
				if ( ! definition ) {
					return null;
				}

				const presetLabel =
					presetColumn && 'object' === typeof presetColumn
						? presetColumn.label
						: null;

				return {
					key: definition.key,
					label: definition.label,
					customLabel:
						presetLabel && presetLabel !== definition.label
							? presetLabel
							: null,
					order: index,
				};
			} )
			.filter( Boolean );
	}

	return columns
		.filter( ( col ) => col.default )
		.map( ( col, index ) => ( {
			key: col.key,
			label: col.label,
			customLabel: null,
			order: index,
		} ) );
}

export default function useQueryBuilder() {
	const [ entity, setEntity ] = useState( null );
	const [ filters, setFilters ] = useState( [] );
	const [ logic, setLogic ] = useState( 'and' );
	const [ step, setStepRaw ] = useState( 0 );
	const [ maxStepReached, setMaxStepReached ] = useState( 0 );

	const setStep = useCallback( ( value ) => {
		setStepRaw( ( prev ) => {
			const next = typeof value === 'function' ? value( prev ) : value;
			setMaxStepReached( ( prevMax ) => Math.max( prevMax, next ) );
			return next;
		} );
	}, [] );
	const cachedCounts = readCachedCounts();
	const [ counts, setCounts ] = useState( cachedCounts );
	const [ countsLoading, setCountsLoading ] = useState( ! cachedCounts );
	const [ countsError, setCountsError ] = useState( null );
	const [ previewCount, setPreviewCount ] = useState( null );
	const [ previewLoading, setPreviewLoading ] = useState( false );
	const [ previewError, setPreviewError ] = useState( null );
	const debounceRef = useRef( null );

	// Column state.
	const [ availableColumns, setAvailableColumns ] = useState( [] );
	const [ columnsLoading, setColumnsLoading ] = useState( false );
	const [ columnsError, setColumnsError ] = useState( null );
	const [ selectedColumns, setSelectedColumns ] = useState( [] );
	const [ exportFormat, setExportFormat ] = useState( 'csv' );
	const pendingColumnsLoadRef = useRef( null );

	// Cross-entity state.
	const [ crossEntityPrimary, setCrossEntityPrimary ] = useState( 'orders' );
	const [ crossEntityRelated, setCrossEntityRelated ] = useState( [ 'customers' ] );
	const crossEntityPrimaryRef = useRef( crossEntityPrimary );
	const crossEntityRelatedRef = useRef( crossEntityRelated );
	crossEntityPrimaryRef.current = crossEntityPrimary;
	crossEntityRelatedRef.current = crossEntityRelated;

	// Custom fields UI state.
	const [ showInternalFields, setShowInternalFields ] = useState( false );
	const [ customFieldsRefreshing, setCustomFieldsRefreshing ] = useState( false );
	const [ columnsRefreshing, setColumnsRefreshing ] = useState( false );
	const showInternalFieldsRef = useRef( false );
	showInternalFieldsRef.current = showInternalFields;

	const countsAbortRef = useRef( null );

	const loadCounts = useCallback( () => {
		if ( countsAbortRef.current ) {
			countsAbortRef.current.abort();
		}
		const controller = new AbortController();
		countsAbortRef.current = controller;

		setCountsLoading( true );
		setCountsError( null );

		return apiFetch( { path: '/rawtype_oe/v1/stats/counts', signal: controller.signal } )
			.then( ( data ) => {
				setCounts( data );
				setCountsLoading( false );
				writeCachedCounts( data );
				return data;
			} )
			.catch( ( err ) => {
				if ( 'AbortError' !== err.name ) {
					setCountsError( err );
					setCountsLoading( false );
				}
			} );
	}, [] );

	useEffect( () => {
		loadCounts();
		return () => {
			if ( countsAbortRef.current ) {
				countsAbortRef.current.abort();
			}
		};
	}, [ loadCounts ] );

	const columnsAbortRef = useRef( null );

	const loadColumns = useCallback(
		( currentEntity, options = {} ) => {
			if ( ! currentEntity ) {
				return Promise.resolve();
			}

			if ( columnsAbortRef.current ) {
				columnsAbortRef.current.abort();
			}
			const controller = new AbortController();
			columnsAbortRef.current = controller;

			const refreshCustomFieldsFlag = !! options.refreshCustomFields;
			const presetColumns = Array.isArray( options.presetColumns ) ? options.presetColumns : null;
			const includeInternal = typeof options.showInternalFields === 'boolean'
				? options.showInternalFields
				: showInternalFieldsRef.current;
			const isRefresh = !! options.isRefresh;

			if ( isRefresh ) {
				setColumnsRefreshing( true );
			} else {
				setColumnsLoading( true );
			}
			setColumnsError( null );

			const customFieldsToColumns = ( customFields ) => {
				const map = {};
				( Array.isArray( customFields ) ? customFields : [] ).forEach( ( field ) => {
					const rawKey = String( field.key || '' );
					const columnKey =
						'string' === typeof field.column_key && field.column_key
							? field.column_key
							: 'meta__' + sanitizeKey( rawKey );
					map[ columnKey ] = {
						key: columnKey,
						label: field.label || rawKey,
						group: 'custom_fields',
						default: false,
						source: field.source || 'meta',
						type: field.type || 'string',
						count: Number.isFinite( field.count ) ? field.count : 0,
						serialized: !! field.serialized,
						is_internal: !! field.is_internal,
					};
				} );
				return map;
			};

			// Fire base columns and custom fields in parallel, but DO NOT
			// block the UI on the custom-fields scan (which can take seconds
			// on huge stores). Show base columns as soon as they arrive and
			// merge in custom fields when ready.
			const columnsRequest = apiFetch( {
				path: '/rawtype_oe/v1/columns/' + currentEntity,
				signal: controller.signal,
			} );

			const customFieldsRequest =
				'cross-entity' === currentEntity
					? Promise.resolve( [] )
					: apiFetch( {
						path:
							'/rawtype_oe/v1/custom-fields/' +
							currentEntity +
							'?show_internal=' +
							( includeInternal ? 'true' : 'false' ) +
							'&refresh=' +
							( refreshCustomFieldsFlag ? 'true' : 'false' ),
						signal: controller.signal,
					} );

			const baseColumnsPromise = columnsRequest
				.then( ( baseColumns ) => {
					const mergedMap = {};
					( Array.isArray( baseColumns ) ? baseColumns : [] ).forEach( ( col ) => {
						if ( col && col.key ) {
							mergedMap[ col.key ] = col;
						}
					} );

					const merged = Object.values( mergedMap );
					const filtered = applyFilters(
						'rawtype_oe_columns_' + currentEntity,
						merged,
					);

					setAvailableColumns( filtered );

					if ( ! isRefresh ) {
						setSelectedColumns( buildSelectedColumns( filtered, presetColumns ) );
						setColumnsLoading( false );
					}

					return filtered;
				} )
				.catch( ( err ) => {
					if ( 'AbortError' !== err.name ) {
						setColumnsError( err );
						if ( ! isRefresh ) {
							setColumnsLoading( false );
						}
					}
					return [];
				} );

			const customFieldsPromise = customFieldsRequest
				.then( ( customFields ) => customFieldsToColumns( customFields ) )
				.catch( ( err ) => {
					if ( 'AbortError' !== err.name ) {
						// Custom-fields failure shouldn't block the wizard.
						return {};
					}
					return null;
				} );

			return Promise.all( [ baseColumnsPromise, customFieldsPromise ] )
				.then( ( [ baseFiltered, customFieldsMap ] ) => {
					if ( null === customFieldsMap ) {
						return;
					}

					const mergedMap = {};
					baseFiltered.forEach( ( col ) => {
						if ( col && col.key ) {
							mergedMap[ col.key ] = col;
						}
					} );
					Object.assign( mergedMap, customFieldsMap );

					const merged = Object.values( mergedMap );
					const filtered = applyFilters(
						'rawtype_oe_columns_' + currentEntity,
						merged,
					);

					setAvailableColumns( filtered );

					if ( isRefresh ) {
						setColumnsRefreshing( false );
					}
				} )
				.catch( () => {
					if ( isRefresh ) {
						setColumnsRefreshing( false );
					}
				} );
		},
		[],
	);

	useEffect( () => {
		if ( entity ) {
			const pendingOptions = pendingColumnsLoadRef.current || {};
			pendingColumnsLoadRef.current = null;
			loadColumns( entity, pendingOptions );
		}
	}, [ entity, loadColumns ] );

	const previewCountAbortRef = useRef( null );

	const fetchPreviewCount = useCallback(
		( currentEntity, currentFilters, currentLogic ) => {
			if ( ! currentEntity ) {
				return;
			}

			const primary = crossEntityPrimaryRef.current;
			const related = crossEntityRelatedRef.current;

			if (
				'cross-entity' === currentEntity &&
				! isValidCrossEntitySelection( primary, related )
			) {
				setPreviewCount( 0 );
				setPreviewError( null );
				return;
			}

			if ( previewCountAbortRef.current ) {
				previewCountAbortRef.current.abort();
			}
			const controller = new AbortController();
			previewCountAbortRef.current = controller;

			setPreviewLoading( true );
			setPreviewError( null );

			const payload = {
				entity: currentEntity,
				filters: currentFilters,
				logic: currentLogic,
				count_only: true,
			};

			if ( 'cross-entity' === currentEntity ) {
				payload.cross_entity = {
					primary_entity: primary,
					related_entities: related,
				};
			}

			apiFetch( {
				path: '/rawtype_oe/v1/preview',
				method: 'POST',
				data: payload,
				signal: controller.signal,
			} )
				.then( ( data ) => {
					setPreviewCount( data.total );
					setPreviewLoading( false );
				} )
				.catch( ( err ) => {
					if ( 'AbortError' !== err.name ) {
						setPreviewError( err );
						setPreviewLoading( false );
					}
				} );
		},
		[],
	);

	// Debounced preview count when filters change.
	useEffect( () => {
		if ( ! entity ) {
			return;
		}

		if ( debounceRef.current ) {
			clearTimeout( debounceRef.current );
		}

		debounceRef.current = setTimeout( () => {
			fetchPreviewCount( entity, filters, logic );
		}, 500 );

		return () => {
			if ( debounceRef.current ) {
				clearTimeout( debounceRef.current );
			}
		};
	}, [ entity, filters, logic, crossEntityPrimary, crossEntityRelated, fetchPreviewCount ] );

	const entityRef = useRef( entity );
	entityRef.current = entity;

	const selectEntity = useCallback( ( newEntity ) => {
		// State updaters must stay pure — branch on the current entity via a
		// ref instead of dispatching from inside the setEntity callback.
		if ( entityRef.current === newEntity ) {
			return;
		}

		pendingColumnsLoadRef.current = null;
		setEntity( newEntity );
		setFilters( [] );
		setPreviewCount( null );
		setSelectedColumns( [] );
		setAvailableColumns( [] );
		setShowInternalFields( false );
		setMaxStepReached( 0 );

		if ( 'cross-entity' === newEntity ) {
			setCrossEntityPrimary( 'orders' );
			setCrossEntityRelated( [ 'customers' ] );
		}
	}, [] );

	const setCrossEntityPrimaryEntity = useCallback( ( primary ) => {
		const nextPrimary = sanitizeKey( primary ) || 'orders';
		setCrossEntityPrimary( nextPrimary );
		setCrossEntityRelated( ( previous ) => previous.filter( ( ent ) => ent !== nextPrimary ) );
	}, [] );

	const toggleCrossEntityRelatedEntity = useCallback( ( relatedEntity ) => {
		const nextEntity = sanitizeKey( relatedEntity );

		if ( ! nextEntity || nextEntity === crossEntityPrimary ) {
			return;
		}

		setCrossEntityRelated( ( previous ) => {
			if ( previous.indexOf( nextEntity ) !== -1 ) {
				return previous.filter( ( entityItem ) => entityItem !== nextEntity );
			}

			return [ ...previous, nextEntity ];
		} );
	}, [ crossEntityPrimary ] );

	const setShowInternalFieldsAndReload = useCallback(
		( enabled ) => {
			const nextEnabled = !! enabled;
			setShowInternalFields( nextEnabled );

			if ( entity && 'cross-entity' !== entity ) {
				loadColumns( entity, { showInternalFields: nextEnabled, isRefresh: true } );
			}
		},
		[ entity, loadColumns ],
	);

	const refreshCustomFields = useCallback( () => {
		if ( ! entity || 'cross-entity' === entity ) {
			return Promise.resolve();
		}

		setCustomFieldsRefreshing( true );
		return loadColumns( entity, { refreshCustomFields: true, isRefresh: true } ).finally( () => {
			setCustomFieldsRefreshing( false );
		} );
	}, [ entity, loadColumns ] );

	const addFilter = useCallback( ( filter ) => {
		setFilters( ( prev ) => [ ...prev, { ...filter, id: Date.now() + Math.random() } ] );
	}, [] );

	const updateFilter = useCallback( ( filterId, updates ) => {
		setFilters( ( prev ) =>
			prev.map( ( f ) => ( f.id === filterId ? { ...f, ...updates } : f ) ),
		);
	}, [] );

	const removeFilter = useCallback( ( filterId ) => {
		setFilters( ( prev ) => prev.filter( ( f ) => f.id !== filterId ) );
	}, [] );

	// Column management.
	const toggleColumn = useCallback( ( column ) => {
		setSelectedColumns( ( prev ) => {
			const exists = prev.find( ( c ) => c.key === column.key );
			if ( exists ) {
				return prev.filter( ( c ) => c.key !== column.key );
			}
			return [
				...prev,
				{
					key: column.key,
					label: column.label,
					customLabel: null,
					order: prev.length,
				},
			];
		} );
	}, [] );

	const selectAllColumns = useCallback( () => {
		setSelectedColumns(
			availableColumns.map( ( col, index ) => ( {
				key: col.key,
				label: col.label,
				customLabel: null,
				order: index,
			} ) ),
		);
	}, [ availableColumns ] );

	const deselectAllColumns = useCallback( () => {
		setSelectedColumns( [] );
	}, [] );

	const resetColumnsToDefaults = useCallback( () => {
		setSelectedColumns( buildSelectedColumns( availableColumns ) );
	}, [ availableColumns ] );

	const renameColumn = useCallback( ( columnKey, customLabel ) => {
		setSelectedColumns( ( prev ) =>
			prev.map( ( c ) =>
				c.key === columnKey ? { ...c, customLabel: customLabel || null } : c,
			),
		);
	}, [] );

	const reorderColumns = useCallback( ( reordered ) => {
		setSelectedColumns( reordered.map( ( c, i ) => ( { ...c, order: i } ) ) );
	}, [] );

	const removeColumn = useCallback( ( columnKey ) => {
		setSelectedColumns( ( prev ) => prev.filter( ( c ) => c.key !== columnKey ) );
	}, [] );

	// Serialize columns for API submission.
	const getColumnsForExport = useCallback( () => {
		return selectedColumns.map( ( c ) => ( {
			key: c.key,
			label: c.customLabel || c.label,
		} ) );
	}, [ selectedColumns ] );

	const goToStep = useCallback(
		( targetStep ) => {
			const idx =
				typeof targetStep === 'number'
					? targetStep
					: WIZARD_STEPS.indexOf( targetStep );
			if ( idx >= 0 && idx < WIZARD_STEPS.length ) {
				setStep( idx );
			}
		},
		[],
	);

	const nextStep = useCallback( () => {
		setStep( ( prev ) => Math.min( prev + 1, WIZARD_STEPS.length - 1 ) );
	}, [] );

	const prevStep = useCallback( () => {
		setStep( ( prev ) => Math.max( prev - 1, 0 ) );
	}, [] );

	const reset = useCallback( () => {
		pendingColumnsLoadRef.current = null;
		setEntity( null );
		setFilters( [] );
		setLogic( 'and' );
		setStep( 0 );
		setMaxStepReached( 0 );
		setPreviewCount( null );
		setSelectedColumns( [] );
		setAvailableColumns( [] );
		setExportFormat( 'csv' );
		setCrossEntityPrimary( 'orders' );
		setCrossEntityRelated( [ 'customers' ] );
		setShowInternalFields( false );
	}, [] );

	/**
	 * Load the query builder from an external configuration (template or profile).
	 *
	 * @param {Object} config Config with entity, filters, columns, format, options.
	 */
	const loadFromConfig = useCallback(
		( config ) => {
			if ( ! config || ! config.entity ) {
				return;
			}

			const nextLoadOptions = {
				presetColumns: Array.isArray( config.columns ) ? config.columns : null,
			};

			setEntity( config.entity );
			setFilters(
				Array.isArray( config.filters )
					? config.filters
						.map( normalizeConfigFilter )
						.filter( Boolean )
						.map( ( f, i ) => ( { ...f, id: Date.now() + i + Math.random() } ) )
					: [],
			);
			setLogic( config.logic || 'and' );
			setExportFormat( config.format || 'csv' );

			if ( config.cross_entity ) {
				setCrossEntityPrimary( sanitizeKey( config.cross_entity.primary_entity ) || 'orders' );
				setCrossEntityRelated(
					Array.isArray( config.cross_entity.related_entities )
						? config.cross_entity.related_entities.map( sanitizeKey ).filter( Boolean )
						: [ 'customers' ],
				);
			}

			if ( entity === config.entity ) {
				loadColumns( config.entity, nextLoadOptions );
			} else {
				pendingColumnsLoadRef.current = nextLoadOptions;
			}
			setStep( config.initialStep ?? 1 );
		},
		[ entity, loadColumns ],
	);

	const crossEntityConfig = useMemo(
		() => ( {
			primary_entity: crossEntityPrimary,
			related_entities: crossEntityRelated,
		} ),
		[ crossEntityPrimary, crossEntityRelated ],
	);

	return {
		// Wizard state.
		step,
		maxStepReached,
		stepName: WIZARD_STEPS[ step ],
		steps: WIZARD_STEPS,
		goToStep,
		nextStep,
		prevStep,

		// Entity.
		entity,
		selectEntity,
		counts,
		countsLoading,
		countsError,
		loadCounts,

		// Cross-entity.
		crossEntityConfig,
		crossEntityPrimary,
		crossEntityRelated,
		crossEntityValid: isValidCrossEntitySelection( crossEntityPrimary, crossEntityRelated ),
		setCrossEntityPrimary: setCrossEntityPrimaryEntity,
		toggleCrossEntityRelated: toggleCrossEntityRelatedEntity,

		// Filters.
		filters,
		logic,
		setLogic,
		addFilter,
		updateFilter,
		removeFilter,

		// Preview.
		previewCount,
		previewLoading,
		previewError,

		// Columns.
		availableColumns,
		columnsLoading,
		columnsError,
		selectedColumns,
		toggleColumn,
		selectAllColumns,
		deselectAllColumns,
		resetColumnsToDefaults,
		renameColumn,
		reorderColumns,
		removeColumn,
		getColumnsForExport,
		showInternalFields,
		setShowInternalFields: setShowInternalFieldsAndReload,
		refreshCustomFields,
		customFieldsRefreshing,
		columnsRefreshing,

		// Export format.
		exportFormat,
		setExportFormat,

		// Misc.
		reset,
		loadFromConfig,
	};
}
