Current File : /home/digitaw/www/wp-content/plugins/event-tickets/src/modules/data/blocks/ticket/selectors.js
/**
 * External dependencies
 */
import { createSelector } from 'reselect';
import { find, trim } from 'lodash';
import moment from 'moment';

/**
 * Internal dependencies
 */
import * as constants from './constants';
import { CAPACITY_TYPE_OPTIONS } from './options';
import { globals } from '@moderntribe/common/utils';

const { UNLIMITED, INDEPENDENT, SHARED, TICKET_TYPES, IS_FREE_TC_TICKET_ALLOWED } = constants;
const { tickets: ticketsConfig, post: postConfig } = globals;

export const getState = ( state ) => state;
export const getBlock = ( state ) => state.tickets.blocks.ticket;

//
// ─── BLOCK SELECTORS ────────────────────────────────────────────────────────────
//

export const getTicketsIsSelected = createSelector( [ getBlock ], ( block ) => block.isSelected );

export const getTicketsIsSettingsOpen = createSelector( [ getBlock ], ( block ) => block.isSettingsOpen );

export const getTicketsIsSettingsLoading = createSelector( [ getBlock ], ( block ) => block.isSettingsLoading );

export const getTicketsProvider = createSelector( [ getBlock ], ( block ) => block.provider );

export const getTicketsSharedCapacity = createSelector( [ getBlock ], ( block ) => block.sharedCapacity );

export const getTicketsSharedCapacityInt = createSelector(
	[ getTicketsSharedCapacity ],
	( capacity ) => parseInt( capacity, 10 ) || 0
);

export const getTicketsTempSharedCapacity = createSelector( [ getBlock ], ( block ) => block.tempSharedCapacity );

export const getTicketsTempSharedCapacityInt = createSelector(
	[ getTicketsTempSharedCapacity ],
	( capacity ) => parseInt( capacity, 10 ) || 0
);

//
// ─── HEADER IMAGE SELECTORS ─────────────────────────────────────────────────────
//

export const getTicketsHeaderImage = createSelector( [ getBlock ], ( block ) => block.headerImage );

export const getTicketsHeaderImageId = createSelector( [ getTicketsHeaderImage ], ( headerImage ) => headerImage.id );

export const getTicketsHeaderImageSrc = createSelector( [ getTicketsHeaderImage ], ( headerImage ) => headerImage.src );

export const getTicketsHeaderImageAlt = createSelector( [ getTicketsHeaderImage ], ( headerImage ) => headerImage.alt );

//
// ─── TICKETS SELECTORS ──────────────────────────────────────────────────────────
//

export const getTickets = createSelector( [ getBlock ], ( block ) => block.tickets );

export const getTicketsAllClientIds = createSelector( [ getTickets ], ( tickets ) => [
	...new Set( tickets.allClientIds ),
] );

export const getTicketsByClientId = createSelector( [ getTickets ], ( tickets ) => tickets.byClientId );

export const getTicketsArray = createSelector( [ getTicketsAllClientIds, getTicketsByClientId ], ( ids, tickets ) =>
	ids.map( ( id ) => tickets[ id ] )
);

export const getTicketsCount = createSelector( [ getTicketsAllClientIds ], ( allClientIds ) => allClientIds.length );

export const hasTickets = createSelector( [ getTicketsCount ], ( count ) => count > 0 );

export const hasCreatedTickets = createSelector( [ getTicketsArray ], ( tickets ) =>
	tickets.reduce( ( hasCreated, ticket ) => hasCreated || ticket.hasBeenCreated, false )
);

export const getIndependentTickets = createSelector( [ getTicketsArray ], ( tickets ) =>
	tickets.filter( ( ticket ) => ticket.details.capacityType === TICKET_TYPES[ INDEPENDENT ] )
);

export const getSharedTickets = createSelector( [ getTicketsArray ], ( tickets ) =>
	tickets.filter( ( ticket ) => ticket.details.capacityType === TICKET_TYPES[ SHARED ] )
);

export const getSharedTicketsCount = createSelector( [ getSharedTickets ], ( tickets ) => tickets.length );

export const getUnlimitedTickets = createSelector( [ getTicketsArray ], ( tickets ) =>
	tickets.filter( ( ticket ) => ticket.details.capacityType === TICKET_TYPES[ UNLIMITED ] )
);

export const hasATicketSelected = createSelector( [ getTicketsArray ], ( tickets ) =>
	tickets.reduce( ( selected, ticket ) => selected || ticket.isSelected, false )
);

export const getTicketsIdsInBlocks = createSelector( [ getTicketsArray ], ( tickets ) =>
	tickets.reduce( ( accumulator, ticket ) => {
		if ( ticket.ticketId !== 0 ) {
			accumulator.push( ticket.ticketId );
		}
		return accumulator;
	}, [] )
);

export const getUneditableTickets = createSelector( [ getBlock ], function ( block ) {
	return block.uneditableTickets || [];
} );

export const getUneditableTicketsAreLoading = createSelector( [ getBlock ], function ( block ) {
	return block.uneditableTicketsLoading || false;
} );

//
// ─── TICKET SELECTORS ───────────────────────────────────────────────────────────
//

export const getTicketClientId = ( state, ownProps ) => ownProps.clientId;

export const getTicket = createSelector(
	[ getTicketsByClientId, getTicketClientId ],
	( tickets, clientId ) => tickets[ clientId ] || {}
);

export const getTicketSold = createSelector( [ getTicket ], ( ticket ) => ticket.sold );

export const getTicketAvailable = createSelector( [ getTicket ], ( ticket ) => ticket.available );

export const getTicketId = createSelector( [ getTicket ], ( ticket ) => ticket.ticketId );

export const getTicketCurrencySymbol = createSelector( [ getTicket ], ( ticket ) => ticket.currencySymbol );

export const getTicketCurrencyPosition = createSelector( [ getTicket ], ( ticket ) => ticket.currencyPosition );

export const getTicketCurrencyDecimalPoint = createSelector( [ getTicket ], ( ticket ) => ticket.currencyDecimalPoint );

export const getTicketCurrencyNumberOfDecimals = createSelector(
	[ getTicket ],
	( ticket ) => ticket.currencyNumberOfDecimals
);

export const getTicketCurrencyThousandsSep = createSelector( [ getTicket ], ( ticket ) => ticket.currencyThousandsSep );

export const getTicketProvider = createSelector( [ getTicket ], ( ticket ) => ticket.provider );

export const getTicketHasAttendeeInfoFields = createSelector(
	[ getTicket ],
	( ticket ) => ticket.hasAttendeeInfoFields
);

export const getTicketIsLoading = createSelector( [ getTicket ], ( ticket ) => ticket.isLoading );

export const getTicketIsModalOpen = createSelector( [ getTicket ], ( ticket ) => ticket.isModalOpen );

export const getTicketHasBeenCreated = createSelector( [ getTicket ], ( ticket ) => ticket.hasBeenCreated );

export const getTicketHasChanges = createSelector( [ getTicket ], ( ticket ) => ticket.hasChanges );

export const getTicketHasDurationError = createSelector( [ getTicket ], ( ticket ) => ticket.hasDurationError );

export const getTicketIsSelected = createSelector( [ getTicket ], ( ticket ) => ticket.isSelected );

export const isTicketDisabled = createSelector(
	[ hasATicketSelected, getTicketIsSelected, getTicketIsLoading, getTicketsIsSettingsOpen ],
	( hasSelected, isSelected, isLoading, isSettingsOpen ) =>
		( hasSelected && ! isSelected ) || isLoading || isSettingsOpen
);

//
// ─── TICKET DETAILS SELECTORS ───────────────────────────────────────────────────
//

export const getTicketDetails = createSelector( [ getTicket ], ( ticket ) => ticket.details || {} );

export const getTicketTitle = createSelector( [ getTicketDetails ], ( details ) => details.title );

export const getTicketDescription = createSelector( [ getTicketDetails ], ( details ) => details.description );

export const getTicketPrice = createSelector( [ getTicketDetails ], ( details ) => details.price );

export const getTicketOnSale = createSelector( [ getTicketDetails ], ( details ) => details.on_sale );

export const getTicketSku = createSelector( [ getTicketDetails ], ( details ) => details.sku );

export const getTicketIACSetting = createSelector( [ getTicketDetails ], ( details ) => details.iac );

export const getTicketStartDate = createSelector( [ getTicketDetails ], ( details ) => details.startDate );

export const getTicketStartDateInput = createSelector( [ getTicketDetails ], ( details ) => details.startDateInput );

export const getTicketStartDateMoment = createSelector( [ getTicketDetails ], ( details ) => details.startDateMoment );

export const getTicketEndDate = createSelector( [ getTicketDetails ], ( details ) => details.endDate );

export const getTicketEndDateInput = createSelector( [ getTicketDetails ], ( details ) => details.endDateInput );

export const getTicketEndDateMoment = createSelector( [ getTicketDetails ], ( details ) => details.endDateMoment );

export const getTicketStartTime = createSelector( [ getTicketDetails ], ( details ) => details.startTime || '' );

export const getTicketStartTimeNoSeconds = createSelector( [ getTicketStartTime ], ( startTime ) =>
	startTime.slice( 0, -3 )
);

export const getTicketEndTime = createSelector( [ getTicketDetails ], ( details ) => details.endTime || '' );

export const getTicketEndTimeNoSeconds = createSelector( [ getTicketEndTime ], ( endTime ) => endTime.slice( 0, -3 ) );

export const getTicketStartTimeInput = createSelector( [ getTicketDetails ], ( details ) => details.startTimeInput );

export const getTicketEndTimeInput = createSelector( [ getTicketDetails ], ( details ) => details.endTimeInput );

export const getTicketCapacityType = createSelector( [ getTicketDetails ], ( details ) => details.capacityType );

export const getTicketCapacity = createSelector( [ getTicketDetails ], ( details ) => details.capacity );

export const getTicketCapacityInt = createSelector(
	[ getTicketCapacity ],
	( capacity ) => parseInt( capacity, 10 ) || 0
);

export const getSalePriceChecked = createSelector( [ getTicketDetails ], ( details ) => details.salePriceChecked );

export const getSalePrice = createSelector( [ getTicketDetails ], ( details ) => details.salePrice );

export const getTicketSaleStartDate = createSelector( [ getTicketDetails ], ( details ) => details.saleStartDate );

export const getTicketSaleStartDateInput = createSelector(
	[ getTicketDetails ],
	( details ) => details.saleStartDateInput
);

export const getTicketSaleStartDateMoment = createSelector(
	[ getTicketDetails ],
	( details ) => details.saleStartDateMoment
);

export const getTicketSaleEndDate = createSelector( [ getTicketDetails ], ( details ) => details.saleEndDate );

export const getTicketSaleEndDateInput = createSelector(
	[ getTicketDetails ],
	( details ) => details.saleEndDateInput
);

export const getTicketSaleEndDateMoment = createSelector(
	[ getTicketDetails ],
	( details ) => details.saleEndDateMoment
);

export const isUnlimitedTicket = createSelector(
	[ getTicketDetails ],
	( details ) => details.capacityType === TICKET_TYPES[ UNLIMITED ]
);

export const isSharedTicket = createSelector(
	[ getTicketDetails ],
	( details ) => details.capacityType === TICKET_TYPES[ SHARED ]
);

export const isIndependentTicket = createSelector(
	[ getTicketDetails ],
	( details ) => details.capacityType === TICKET_TYPES[ INDEPENDENT ]
);

export const isTicketPast = createSelector( [ getTicketEndDateMoment ], ( endDate ) => moment().isAfter( endDate ) );

export const isTicketFuture = createSelector( [ getTicketStartDateMoment ], ( startDate ) =>
	moment().isBefore( startDate )
);

export const isTicketOnSale = createSelector(
	[ getTicketHasBeenCreated, isTicketPast, isTicketFuture ],
	( hasBeenCreated, isPast, isFuture ) => hasBeenCreated && ! isPast && ! isFuture
);

export const hasTicketOnSale = createSelector( [ getTicketsAllClientIds, getState ], ( allClientIds, state ) =>
	allClientIds.reduce( ( onSale, clientId ) => onSale || isTicketOnSale( state, { clientId } ), false )
);

export const allTicketsPast = createSelector( [ getTicketsAllClientIds, getState ], ( allClientIds, state ) =>
	allClientIds.reduce( ( isPast, clientId ) => {
		const props = { clientId };
		return getTicketHasBeenCreated( state, props ) ? isPast && isTicketPast( state, props ) : isPast;
	}, true )
);

export const allTicketsFuture = createSelector( [ getTicketsAllClientIds, getState ], ( allClientIds, state ) =>
	allClientIds.reduce( ( isFuture, clientId ) => {
		const props = { clientId };
		return getTicketHasBeenCreated( state, props ) ? isFuture && isTicketFuture( state, props ) : isFuture;
	}, true )
);

export const getTicketAttendeeInfoFields = createSelector(
	[ getTicketDetails ],
	( details ) => details.attendeeInfoFields || []
);

//
// ─── TICKET TEMP DETAILS SELECTORS ──────────────────────────────────────────────
//

export const getTicketTempDetails = createSelector( [ getTicket ], ( ticket ) => ticket.tempDetails || {} );

export const getTicketTempTitle = createSelector( [ getTicketTempDetails ], ( tempDetails ) => tempDetails.title );

export const getTicketTempDescription = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.description
);

export const getTicketTempPrice = createSelector( [ getTicketTempDetails ], ( tempDetails ) => tempDetails.price );

export const getTicketTempSku = createSelector( [ getTicketTempDetails ], ( tempDetails ) => tempDetails.sku );

export const getTicketTempIACSetting = createSelector( [ getTicketTempDetails ], ( tempDetails ) => tempDetails.iac );

export const getTicketTempStartDate = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.startDate
);

export const getTicketTempStartDateInput = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.startDateInput
);

export const getTicketTempStartDateMoment = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.startDateMoment
);

export const getTicketTempEndDate = createSelector( [ getTicketTempDetails ], ( tempDetails ) => tempDetails.endDate );

export const getTicketTempEndDateInput = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.endDateInput
);

export const getTicketTempEndDateMoment = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.endDateMoment
);

export const getTicketTempStartTime = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.startTime || ''
);

export const getTicketTempStartTimeNoSeconds = createSelector( [ getTicketTempStartTime ], ( startTime ) =>
	startTime.slice( 0, -3 )
);

export const getTicketTempEndTime = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.endTime || ''
);

export const getTicketTempEndTimeNoSeconds = createSelector( [ getTicketTempEndTime ], ( endTime ) =>
	endTime.slice( 0, -3 )
);

export const getTicketTempStartTimeInput = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.startTimeInput
);

export const getTicketTempEndTimeInput = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.endTimeInput
);

export const getTicketTempCapacityType = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.capacityType
);

export const getTicketTempCapacity = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.capacity
);

export const getTicketTempCapacityInt = createSelector(
	[ getTicketTempCapacity ],
	( capacity ) => parseInt( capacity, 10 ) || 0
);

export const getTicketTempCapacityTypeOption = createSelector(
	[ getTicketTempCapacityType ],
	( capacityType ) => find( CAPACITY_TYPE_OPTIONS, { value: capacityType } ) || {}
);

export const getTempSalePriceChecked = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.salePriceChecked
);

export const getTempSalePrice = createSelector( [ getTicketTempDetails ], ( tempDetails ) => tempDetails.salePrice );

export const getTicketTempSaleStartDate = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.saleStartDate
);

export const getTicketTempSaleStartDateInput = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.saleStartDateInput
);

export const getTicketTempSaleStartDateMoment = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.saleStartDateMoment
);
export const getTicketTempSaleEndDate = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.saleEndDate
);

export const getTicketTempSaleEndDateInput = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.saleEndDateInput
);

export const getTicketTempSaleEndDateMoment = createSelector(
	[ getTicketTempDetails ],
	( tempDetails ) => tempDetails.saleEndDateMoment
);

export const showSalePrice = createSelector( [ getTicketsProvider ], ( provider ) => {
	return provider === constants.TICKETS_COMMERCE_MODULE_CLASS || provider === constants.WOO_CLASS;
} );

export const isTicketSalePriceValid = createSelector(
	[
		getTempSalePrice,
		getTicketTempPrice,
		getTicketCurrencyDecimalPoint,
		getTicketCurrencyNumberOfDecimals,
		getTicketCurrencyThousandsSep,
	],
	( salePrice, price, decimalPoint, decimalPlaces, thousandSep ) => {
		if ( salePrice === '' || price === '' ) {
			return true;
		}

		if ( ! decimalPoint || ! decimalPlaces || ! thousandSep ) {
			return true;
		}

		// eslint-disable-next-line no-use-before-define
		const salePriceVal = getNumericPrice( salePrice, decimalPoint, decimalPlaces, thousandSep );
		// eslint-disable-next-line no-use-before-define
		const priceVal = getNumericPrice( price, decimalPoint, decimalPlaces, thousandSep );

		return salePriceVal < priceVal;
	}
);

export const isTempTitleValid = createSelector( [ getTicketTempTitle ], ( title ) => trim( title ) !== '' );

export const isTempCapacityValid = createSelector(
	[ getTicketTempCapacity ],
	( capacity ) => trim( capacity ) !== '' && ! isNaN( capacity ) && capacity > 0
);

export const isTempSharedCapacityValid = createSelector(
	[ getTicketsTempSharedCapacity ],
	( capacity ) => trim( capacity ) !== '' && ! isNaN( capacity ) && capacity > 0
);

export const isZeroPriceValid = createSelector( [ getTicketTempPrice, getTicketsProvider ], ( price, provider ) => {
	if ( 0 < parseInt( price, 10 ) ) {
		return true;
	}
	if ( constants.TC_CLASS === provider ) {
		return false;
	}
	if ( constants.TICKETS_COMMERCE_MODULE_CLASS === provider ) {
		return IS_FREE_TC_TICKET_ALLOWED;
	}
	return true;
} );

export const isTicketValid = createSelector(
	[ getTicketTempCapacityType, isTempTitleValid, isTempCapacityValid, isTempSharedCapacityValid, isZeroPriceValid ],
	( capacityType, titleValid, capacityValid, sharedCapacityValid, zeroPriceValid ) => {
		if ( capacityType === TICKET_TYPES[ UNLIMITED ] ) {
			return titleValid && zeroPriceValid;
		} else if ( capacityType === TICKET_TYPES[ SHARED ] ) {
			return titleValid && sharedCapacityValid && zeroPriceValid;
		}
		return titleValid && capacityValid && zeroPriceValid;
	}
);

//
// ─── AMOUNT REDUCERS ────────────────────────────────────────────────────────────
//

export const _getTotalCapacity = ( tickets ) =>
	tickets.reduce( ( total, ticket ) => {
		const capacity = parseInt( ticket.details.capacity, 10 ) || 0;
		return total + capacity;
	}, 0 );

export const _getTotalTempCapacity = ( tickets ) =>
	tickets.reduce( ( total, ticket ) => {
		const tempCapacity = parseInt( ticket.tempDetails.capacity, 10 ) || 0;
		return total + tempCapacity;
	}, 0 );

export const _getTotalSold = ( tickets ) =>
	tickets.reduce( ( total, ticket ) => {
		const sold = parseInt( ticket.sold, 10 ) || 0;
		return total + sold;
	}, 0 );

export const _getTotalAvailable = ( tickets ) =>
	tickets.reduce( ( total, ticket ) => {
		const available = parseInt( ticket.available, 10 ) || 0;
		return total + available;
	}, 0 );

export const getIndependentTicketsCapacity = createSelector( getIndependentTickets, _getTotalCapacity );
export const getIndependentTicketsTempCapacity = createSelector( getIndependentTickets, _getTotalTempCapacity );
export const getIndependentTicketsSold = createSelector( getIndependentTickets, _getTotalSold );
export const getIndependentTicketsAvailable = createSelector( getIndependentTickets, _getTotalAvailable );

export const getSharedTicketsSold = createSelector( getSharedTickets, _getTotalSold );

export const getSharedTicketsAvailable = createSelector(
	[ getTicketsSharedCapacityInt, getSharedTicketsSold ],
	( sharedCapacity, sharedSold ) => Math.max( sharedCapacity - sharedSold, 0 )
);

export const getIndependentAndSharedTicketsCapacity = createSelector(
	[ getIndependentTicketsCapacity, getTicketsSharedCapacityInt ],
	( independentCapacity, sharedCapacity ) => independentCapacity + sharedCapacity
);
export const getIndependentAndSharedTicketsTempCapacity = createSelector(
	[ getIndependentTicketsTempCapacity, getTicketsTempSharedCapacityInt ],
	( independentTempCapacity, tempSharedCapacity ) => independentTempCapacity + tempSharedCapacity
);
export const getIndependentAndSharedTicketsSold = createSelector(
	[ getIndependentTicketsSold, getSharedTicketsSold ],
	( independentSold, sharedSold ) => independentSold + sharedSold
);
export const getIndependentAndSharedTicketsAvailable = createSelector(
	[ getIndependentTicketsAvailable, getSharedTicketsAvailable ],
	( independentAvailable, sharedAvailable ) => independentAvailable + sharedAvailable
);

//
// ─── MISC SELECTORS ─────────────────────────────────────────────────────────────
//

export const getTicketProviders = () => {
	const tickets = ticketsConfig();
	return tickets.providers || [];
};

export const getDefaultTicketProvider = () => {
	const tickets = ticketsConfig();
	return tickets.default_provider || '';
};

export const hasValidTicketProvider = () => {
	const provider = getDefaultTicketProvider();
	return provider !== '' && provider !== constants.RSVP_CLASS;
};

export const hasMultipleTicketProviders = createSelector(
	[ getTicketProviders ],
	( providers ) => providers.length > 1
);

export const hasTicketProviders = createSelector( [ getTicketProviders ], ( providers ) => providers.length > 0 );

export const canCreateTickets = createSelector(
	[ hasTicketProviders, hasValidTicketProvider ],
	( providers, validDefaultProvider ) => providers && validDefaultProvider
);

export const getCurrentPostTypeLabel = ( key = 'singular_name' ) => {
	const post = postConfig();
	return post?.labels?.[ key ] || 'Post';
};

export const currentPostIsEvent = () => {
	const post = postConfig();
	return post?.type === 'tribe_events';
};

export const getNumericPrice = ( price, decimalPoint, decimalPlaces, thousandSep ) => {
	if ( typeof price !== 'string' ) {
		price = String( price );
	}
	
	// Remove thousand separator.
	let newValue = price.replace( new RegExp( '\\' + thousandSep, 'g' ), '' );

	// Replace decimal separator with period.
	newValue = newValue.replace( decimalPoint, '.' );

	// Round to specified number of decimal places.
	newValue = parseFloat( newValue ).toFixed( decimalPlaces );
	newValue = parseInt( newValue.replace( '.', '' ) );

	return newValue;
};