Current File : //home/abedqjib/krishanfoundation.com/wp-content/plugins/surerank/src/store/actions.js
import { pick } from 'lodash';
import { select } from '@wordpress/data';
import { addQueryArgs } from '@wordpress/url';

import { STORE_NAME } from './constants';
import * as actionTypes from './action-types';
import { EDITOR_URL } from '@Global/constants/api';
import {
	getCategorizedChecks,
	getCheckTypeKey,
	mergeAllCheckTypes,
} from '@/functions/utils';
import { CHECK_TYPES } from '@/global/constants';
/**
 * Returns an action object used in signalling that viewport queries have been
 * updated. Values are specified as an object of breakpoint query keys where
 * value represents whether query matches.
 * Ignored from documentation as it is for internal use only.
 *
 * @param {string} value Value to update.
 */
export function updatePostSeoMeta( value ) {
	return {
		type: 'UPDATE_POST_SEO_META',
		value,
	};
}

export function updateMetaboxState( value ) {
	return {
		type: 'UPDATE_METABOX_STATE',
		value,
	};
}

export function updateModalState( value ) {
	return {
		type: 'UPDATE_MODAL_STATE',
		value,
	};
}

// To create content dynamically.
export function updatePostDynamicData( value ) {
	return {
		type: 'UPDATE_DYNAMIC_DATA',
		value,
	};
}

export function* updatePostMetaData( value ) {
	yield setUnsavedMetaSettings( value );

	return {
		type: actionTypes.UPDATE_META_DATA,
		value,
	};
}

export function updateInitialState( value ) {
	return {
		type: 'UPDATE_INITIAL_STATE',
		value,
	};
}

export const updateGlobalDefaults = ( payload ) => ( {
	type: actionTypes.UPDATE_GLOBAL_DEFAULTS,
	payload,
} );

export function* initMetaDataAndDefaults( { postSeoMeta, globalDefaults } ) {
	let postSeoMetaObj = postSeoMeta;
	if ( postSeoMetaObj && ! Object.keys( postSeoMetaObj ).length ) {
		postSeoMetaObj = select( STORE_NAME ).getPostSeoMeta();
		postSeoMetaObj = pick( globalDefaults, Object.keys( postSeoMetaObj ) );
		yield updatePostMetaData( postSeoMetaObj );
	} else {
		yield updatePostSeoMeta( postSeoMetaObj );
	}
	return updateGlobalDefaults( globalDefaults );
}

export function updateAppSettings( value ) {
	return {
		type: actionTypes.UPDATE_APP_SETTINGS,
		value,
	};
}

export const setPageSeoChecks = ( payload ) => {
	return {
		type: actionTypes.SET_PAGE_SEO_CHECKS,
		payload,
	};
};

export const setPageSeoCheck = ( key, value ) => {
	let payload = { [ key ]: value };

	if ( key === 'checks' ) {
		const state = select( STORE_NAME ).getState();
		const ignoredList = state.pageSeoChecks?.ignoredList || [];

		const categorizedChecks = getCategorizedChecks( value, ignoredList );

		payload = {
			checks: value,
			categorizedChecks,
		};
	} else if ( CHECK_TYPES.includes( key ) ) {
		// Handle any check type dynamically
		const state = select( STORE_NAME ).getState();
		const ignoredList = state.pageSeoChecks?.ignoredList || [];

		const allChecks =
			mergeAllCheckTypes( state, key, value )?.filter( Boolean ) || [];
		const categorizedChecks = getCategorizedChecks(
			allChecks,
			ignoredList
		);
		const categorizedCheckType = getCategorizedChecks( value, ignoredList );
		const storeKeys = getCheckTypeKey( key );

		payload = {
			[ storeKeys.type ]: value,
			checks: allChecks,
			categorizedChecks,
			[ storeKeys.categorizedType ]: categorizedCheckType,
		};
	}

	return {
		type: actionTypes.SET_PAGE_SEO_CHECK,
		payload,
	};
};

export const setUnsavedMetaSettings = ( payload ) => {
	return {
		type: actionTypes.SET_UNSAVED_META_SETTINGS,
		payload,
	};
};

export const resetUnsavedMetaSettings = () => {
	return {
		type: actionTypes.RESET_UNSAVED_META_SETTINGS,
	};
};

export const setRefreshCalled = ( value ) => ( {
	type: actionTypes.SET_REFRESH_CALLED,
	value,
} );

export const setCurrentPostIgnoredList = ( payload ) => ( {
	type: actionTypes.SET_CURRENT_POST_IGNORED_LIST,
	payload,
} );

export function fetchFromAPI( payload ) {
	return {
		type: actionTypes.FETCH_FROM_API,
		payload,
	};
}

export function* restoreIgnoreCheck( checkId, actionType ) {
	const state = select( STORE_NAME ).getState();
	const postId =
		state.pageSeoChecks?.postId ||
		state.variables?.post?.ID?.value ||
		state.variables?.term?.ID?.value;
	const postType =
		window?.surerank_seo_popup?.is_taxonomy === '1' ? 'taxonomy' : 'post';

	try {
		const data = yield fetchFromAPI( {
			path: 'surerank/v1/checks/ignore-page-check',
			method: actionType === 'ignore' ? 'POST' : 'DELETE',
			data: { post_id: postId, id: checkId, check_type: postType },
		} );

		// Update ignoredList with the array of IDs
		yield setCurrentPostIgnoredList( data?.checks );

		const seoChecksState = select( STORE_NAME ).getPageSeoChecks();
		const checkType = seoChecksState.checks.find(
			( check ) => check.id === checkId
		)?.type;
		const storeKey = getCheckTypeKey( checkType )?.type || 'checks';
		yield setPageSeoCheck( checkType, seoChecksState[ storeKey ] );

		// Sync back to seo-bar per-post cache so the badge reflects the change.
		const allChecks = select( STORE_NAME ).getPageSeoChecks()?.checks || [];
		if ( postId && allChecks.length > 0 ) {
			yield setPageSeoChecksByIdAndType( postId, postType, allChecks );
		}
	} catch ( error ) {
		// Silently fail for aborted requests
	}
}

export function* ignorePageSeoCheck( checkId ) {
	yield restoreIgnoreCheck( checkId, 'ignore' );
}

export function* restorePageSeoCheck( checkId ) {
	yield restoreIgnoreCheck( checkId, 'restore' );
}

export const setPageSeoChecksByIdAndType = (
	postId,
	postType,
	checks,
	error = null
) => {
	const { categorizedChecks, sequence } = categorizeChecksList( checks );

	return {
		type: actionTypes.SET_PAGE_SEO_CHECKS_BY_ID_AND_TYPE,
		payload: {
			postId,
			postType,
			checks: categorizedChecks,
			sequence,
			error,
		},
	};
};

export const setBatchPageSeoChecks = ( data, type = 'post' ) => {
	const processedData = {};
	Object.entries( data ).forEach( ( [ id, itemData ] ) => {
		const checks = itemData.checks || {};
		const processedChecks = Object.entries( checks ).map(
			( [ key, value ] ) => ( {
				...value,
				id: key,
				title:
					value?.message ||
					key
						.replace( /_/g, ' ' )
						.replace( /\b\w/g, ( c ) => c.toUpperCase() ),
				data: value?.description,
				showImages: key === 'image_alt_text',
			} )
		);

		const { categorizedChecks, sequence } =
			categorizeChecksList( processedChecks );

		processedData[ id ] = {
			postType: type,
			checks: categorizedChecks,
			sequence,
			error: null,
		};
	} );

	return {
		type: actionTypes.SET_BATCH_PAGE_SEO_CHECKS,
		payload: processedData,
	};
};

const categorizeChecksList = ( checks ) => {
	const sequence = [];
	const categorizedChecks = checks.reduce(
		( acc, check ) => {
			// For preserving the order of the checks
			sequence.push( check.id );

			if ( check?.ignore ) {
				acc.ignoredChecks.push( check );
			} else {
				// set the flag to false to show the check in the UI
				check.ignore = false;

				if ( check.status === 'error' ) {
					acc.badChecks.push( check );
				} else if ( check.status === 'warning' ) {
					acc.fairChecks.push( check );
				} else if ( check.status === 'suggestion' ) {
					acc.suggestionChecks.push( check );
				} else if ( check.status === 'success' ) {
					acc.passedChecks.push( check );
				}
			}
			return acc;
		},
		{
			badChecks: [],
			fairChecks: [],
			suggestionChecks: [],
			passedChecks: [],
			ignoredChecks: [],
		}
	);

	return { categorizedChecks, sequence };
};

function* handleSeoBarCheckIgnoreUpdate(
	checkId,
	postId,
	postType,
	method,
	value
) {
	try {
		const response = yield fetchFromAPI( {
			path: 'surerank/v1/checks/ignore-page-check',
			method,
			data: { post_id: postId, id: checkId, check_type: postType },
		} );

		if ( response?.status !== 'success' ) {
			throw new Error( response?.message );
		}

		const { checks, sequence } = select( STORE_NAME ).getSeoBarChecks(
			postId,
			postType
		);
		const flatChecks = Object.values( checks )
			.flat()
			.map( ( check ) => {
				if ( check.id === checkId ) {
					check.ignore = value;
				}
				return check;
			} )
			.sort(
				( a, b ) => sequence.indexOf( a.id ) - sequence.indexOf( b.id )
			);

		yield setPageSeoChecksByIdAndType( postId, postType, flatChecks );
	} catch ( error ) {
		// Silently fail for aborted requests
	}
}

export function* ignoreSeoBarCheck( checkId, postId, postType ) {
	yield handleSeoBarCheckIgnoreUpdate(
		checkId,
		postId,
		postType,
		'POST',
		true
	);
}

export function* restoreSeoBarCheck( checkId, postId, postType ) {
	yield handleSeoBarCheckIgnoreUpdate(
		checkId,
		postId,
		postType,
		'DELETE',
		false
	);
}

/**
 * Converts the seo-bar's per-post categorized check cache into the flat format
 * expected by the modal, then seeds pageSeoChecks so analyze.js skips the
 * duplicate refreshPageChecks API call (refreshCalled: true).
 *
 * @param {string|number} postId     The post or term ID.
 * @param {Object}        seoBarData The cached seo-bar entry: { checks, sequence }.
 */
const seedModalFromSeoBarCache = ( postId, seoBarData ) => {
	const { checks: categorized, sequence } = seoBarData;

	// Flatten the seo-bar's categorized buckets into a single sorted array.
	const allChecks = [
		'badChecks',
		'fairChecks',
		'suggestionChecks',
		'passedChecks',
		'ignoredChecks',
	]
		.flatMap( ( key ) => categorized[ key ] || [] )
		.sort(
			( a, b ) => sequence.indexOf( a.id ) - sequence.indexOf( b.id )
		);

	const pageChecks = allChecks.filter( ( c ) => c.type === 'page' );
	const keywordChecks = allChecks.filter( ( c ) => c.type === 'keyword' );
	const ignoredList = allChecks
		.filter( ( c ) => c.ignore )
		.map( ( c ) => c.id );

	const categorizedChecks = getCategorizedChecks( allChecks, ignoredList );
	const categorizedPageChecks = getCategorizedChecks(
		pageChecks,
		ignoredList
	);
	const categorizedKeywordChecks = getCategorizedChecks(
		keywordChecks,
		ignoredList
	);

	return setPageSeoChecks( {
		postId,
		checks: allChecks,
		pageChecks,
		keywordChecks,
		categorizedChecks,
		categorizedPageChecks,
		categorizedKeywordChecks,
		ignoredList,
		initializing: false,
		refreshCalled: true,
		isRefreshing: false,
	} );
};

/**
 * Reset the store for a new post context on listing pages.
 * Clears modal-specific state while preserving per-post seo-bar check cache.
 *
 * @param {string|number} postId     The post or term ID being opened.
 * @param {string}        postType   'post' or 'taxonomy'.
 * @param {boolean}       isTaxonomy Whether this is a taxonomy term.
 */
export function* resetForNewPost( postId, postType, isTaxonomy ) {
	yield {
		type: actionTypes.RESET_FOR_NEW_POST,
		payload: { postId },
	};

	// If the seo-bar already fetched checks for this post, seed the modal state
	// so analyze.js skips the duplicate refreshPageChecks API call.
	// RESET_FOR_NEW_POST preserves numeric-keyed cache entries, so we read after.
	const cachedData = select( STORE_NAME ).getPageSeoChecks()?.[ postId ];
	if ( cachedData?.checks ) {
		yield seedModalFromSeoBarCache( postId, cachedData );
	}

	// Fetch editor variables (used for title/description template replacement).
	const queryParams = isTaxonomy ? { term_id: postId } : { post_id: postId };

	try {
		const response = yield fetchFromAPI( {
			path: addQueryArgs( EDITOR_URL, queryParams ),
			method: 'GET',
		} );

		if ( response?.success ) {
			let initialState = { variables: response.variables };
			if ( response.other ) {
				initialState = { ...initialState, ...response.other };
			}
			yield updateInitialState( initialState );
		}
	} catch ( error ) {
		// Silently fail — the modal will still work without variables.
	}
}