/**
 * Shared helpers for collection repeaters (cards, tabs, FAQ, plans, …)
 * — same Customization pattern as Hero Slides | General.
 */

import { __, sprintf } from '@wordpress/i18n';
import { normalizeLink, normalizeMedia } from '../interpolate';

const BUTTON_LOGO_SLIDE = new Set( [ 'button', 'logo', 'slide' ] );

/** Attr keys that stay in sync across every testimonial card. */
export const SHARED_COLLECTION_ICON_KEYS = Object.freeze( [
	'icon',
	'iconSize',
] );

/**
 * Testimonials (and packs with sharedIcon) use one quote icon for all cards.
 *
 * @param {object} field Collection repeater field
 * @return {boolean}
 */
export function collectionSharesIcon( field = {} ) {
	if ( field?.sharedIcon === true || field?.syncIcon === true ) {
		return true;
	}
	return String( field?.itemType || '' ) === 'testimonial';
}

/**
 * @param {object} partial
 * @return {boolean}
 */
export function partialTouchesSharedIcon( partial = {} ) {
	return Object.keys( partial || {} ).some( ( key ) =>
		SHARED_COLLECTION_ICON_KEYS.includes( key )
	);
}

/**
 * Pick only shared-icon keys from a partial update.
 *
 * @param {object} partial
 * @return {object}
 */
export function pickSharedIconPartial( partial = {} ) {
	const out = {};
	SHARED_COLLECTION_ICON_KEYS.forEach( ( key ) => {
		if ( Object.prototype.hasOwnProperty.call( partial || {}, key ) ) {
			out[ key ] = partial[ key ];
		}
	} );
	return out;
}

/**
 * Copy shared icon attrs onto a new/updated item from siblings or defaults.
 *
 * @param {object}        item
 * @param {object}        field
 * @param {Array<object>} [siblings]
 * @return {object}
 */
export function applySharedCollectionIcon( item = {}, field = {}, siblings = [] ) {
	if ( ! collectionSharesIcon( field ) ) {
		return item;
	}
	const list = Array.isArray( siblings ) ? siblings : [];
	const from =
		list.find( ( row ) => String( row?.icon || '' ).trim() ) ||
		list[ 0 ] ||
		null;
	const fallback =
		String( field.sharedIconDefault || '' ).trim() ||
		'dashicons:format-quote';
	const next = { ...item };
	next.icon = String( from?.icon || '' ).trim() || fallback;
	if ( from && Object.prototype.hasOwnProperty.call( from, 'iconSize' ) ) {
		next.iconSize = from.iconSize;
	}
	return next;
}

/**
 * Whether delete/duplicate chrome should show for the focused collection item.
 * - Tabs / accordion: only when the nav title is selected — not panel body content.
 * - Cards / testimonials / other shells: only when the card parent is selected
 *   (no nested element key), since the container itself is activatable.
 *
 * @param {object}                field               Collection repeater field
 * @param {string|null|undefined} selectedElementKey
 * @return {boolean}
 */
export function collectionItemChromeActionsAllowed(
	field = {},
	selectedElementKey = null
) {
	const itemType = String( field?.itemType || '' ).toLowerCase();
	const fieldKey = String( field?.key || '' );
	const selected =
		selectedElementKey == null || selectedElementKey === ''
			? null
			: String( selectedElementKey );

	if ( itemType === 'tab' || fieldKey === 'tabs' ) {
		return selected === 'tabTitle';
	}
	if ( itemType === 'accordion' ) {
		return selected === 'accordionTitle';
	}

	return selected == null;
}

/**
 * Primary content-list repeater (not buttons/logos/slides).
 *
 * @param {Array<object>} fieldSchema
 * @return {object|null}
 */
export function findCollectionRepeater( fieldSchema = [] ) {
	const list = Array.isArray( fieldSchema ) ? fieldSchema : [];
	const match = list.find( ( field ) => {
		if ( ! field?.key || field.type !== 'repeater' || field.styleOnly ) {
			return false;
		}
		const itemType = String( field.itemType || '' );
		if ( BUTTON_LOGO_SLIDE.has( itemType ) ) {
			return false;
		}
		// Prefer explicit collection itemTypes; also allow key-based lists.
		if ( itemType ) {
			return true;
		}
		return [ 'cards', 'items', 'tabs', 'plans' ].includes( field.key );
	} );
	return match || null;
}

/**
 * Pricing matrix (and My Blocks cloned from it) — element reorder is pricing-only.
 * User blocks keep a custom slug; match systemSlug / category as well.
 *
 * @param {object} meta
 * @return {boolean}
 */
export function isPricingCollectionBlock( meta = {} ) {
	if ( ! meta || typeof meta !== 'object' ) {
		return false;
	}
	if (
		meta.slug === 'pricing' ||
		meta.systemSlug === 'pricing' ||
		meta.categorySlug === 'pricing'
	) {
		return true;
	}
	if (
		Array.isArray( meta.categorySlugs ) &&
		meta.categorySlugs.includes( 'pricing' )
	) {
		return true;
	}
	const collection = findCollectionRepeater( meta.fieldSchema || [] );
	if (
		collection?.key === 'plans' ||
		collection?.itemType === 'plan' ||
		String( collection?.itemsLabel || '' )
			.toLowerCase()
			.includes( 'pricing' )
	) {
		return true;
	}
	const markup = String( meta.markup || '' );
	if ( markup.includes( 'bm-pricing' ) ) {
		return true;
	}
	return false;
}

/**
 * Tab / panel title e.g. "FAQ Items", "Cards", "Tabs".
 *
 * @param {object} meta  Block meta (name, …)
 * @param {object} field Collection repeater field
 * @return {string}
 */
export function collectionItemsLabel( meta = {}, field = {} ) {
	if ( field.itemsLabel ) {
		return String( field.itemsLabel );
	}
	if ( field.panelLabel ) {
		return String( field.panelLabel );
	}
	const name = String( meta.name || '' );
	const afterDash = name.includes( '—' )
		? name.split( '—' ).pop().trim()
		: name.includes( '|' )
			? name.split( '|' ).pop().trim()
			: '';
	if ( afterDash ) {
		return sprintf(
			/* translators: %s: block short name e.g. FAQ */
			__( '%s Items', 'joc-blockora-morph' ),
			afterDash
		);
	}
	if ( field.label ) {
		const label = String( field.label );
		if ( /items?$/i.test( label ) ) {
			return label;
		}
		return sprintf(
			/* translators: %s: repeater label e.g. Cards */
			__( '%s Items', 'joc-blockora-morph' ),
			label
		);
	}
	return __( 'Items', 'joc-blockora-morph' );
}

/**
 * Singular noun for one row ("Card", "Tab", "Item").
 *
 * @param {object} field
 * @return {string}
 */
export function collectionItemNoun( field = {} ) {
	if ( field.itemLabel ) {
		return String( field.itemLabel );
	}
	const map = {
		card: __( 'Card', 'joc-blockora-morph' ),
		tab: __( 'Tab', 'joc-blockora-morph' ),
		plan: __( 'Plan', 'joc-blockora-morph' ),
		testimonial: __( 'Testimonial', 'joc-blockora-morph' ),
		accordion: __( 'Item', 'joc-blockora-morph' ),
		feature: __( 'Feature', 'joc-blockora-morph' ),
	};
	const itemType = String( field.itemType || '' );
	if ( map[ itemType ] ) {
		return map[ itemType ];
	}
	return __( 'Item', 'joc-blockora-morph' );
}

/**
 * Plain-text preview for list rows (strips tags + decodes entities).
 *
 * @param {unknown} value
 * @return {string}
 */
export function stripListPreviewText( value ) {
	let text = String( value || '' );
	text = text.replace( /<[^>]*>/g, ' ' );
	text = text
		.replace( /&nbsp;/gi, ' ' )
		.replace( /&amp;/gi, '&' )
		.replace( /&lt;/gi, '<' )
		.replace( /&gt;/gi, '>' )
		.replace( /&quot;/gi, '"' )
		.replace( /&#0*39;/g, "'" )
		.replace( /&#x0*27;/gi, "'" )
		.replace( /&#(\d+);/g, ( _, n ) => {
			const code = Number( n );
			return Number.isFinite( code ) ? String.fromCharCode( code ) : '';
		} )
		.replace( /&#x([0-9a-f]+);/gi, ( _, hex ) => {
			const code = parseInt( hex, 16 );
			return Number.isFinite( code ) ? String.fromCharCode( code ) : '';
		} );
	text = text.replace( /<[^>]*>/g, ' ' );
	return text.replace( /\s+/g, ' ' ).trim();
}

/**
 * Short preview line for a collection row.
 *
 * @param {object} item
 * @return {string}
 */
export function collectionItemPreview( item = {} ) {
	const lines = collectionItemListLines( item );
	return lines.title || lines.description || lines.fallback || '';
}

/**
 * Title + description lines for collection list rows (Fields / Design).
 *
 * @param {object} item
 * @return {{ title: string, description: string, fallback: string }}
 */
export function collectionItemListLines( item = {} ) {
	const title = stripListPreviewText(
		item?.title ||
			item?.tabTitle ||
			item?.accordionTitle ||
			item?.header ||
			item?.name ||
			item?.tag ||
			''
	);
	const description = stripListPreviewText( item?.description || '' );
	const fallback = stripListPreviewText(
		item?.content || item?.text || item?.label || ''
	);
	return { title, description, fallback };
}

/**
 * Seed a new collection item from nested field schema.
 *
 * @param {object}        field Collection repeater field
 * @param {number}        [index]
 * @param {Array<object>} [siblings] Existing items (for shared icon)
 * @return {object}
 */
export function createDefaultCollectionItem(
	field = {},
	index = 0,
	siblings = []
) {
	const item = {};
	const fields = Array.isArray( field.fields ) ? field.fields : [];
	fields.forEach( ( f ) => {
		if ( ! f?.key ) {
			return;
		}
		if ( f.type === 'link' ) {
			item[ f.key ] = normalizeLink(
				f.default && typeof f.default === 'object' ? f.default : {}
			);
		} else if ( f.type === 'media' ) {
			item[ f.key ] = normalizeMedia(
				f.default && typeof f.default === 'object'
					? f.default
					: { id: 0, url: '', alt: '', mime: '', type: 'image' }
			);
		} else if ( f.type === 'toggle' || f.type === 'boolean' ) {
			item[ f.key ] = Boolean( f.default );
		} else if ( f.type === 'number' ) {
			item[ f.key ] =
				f.default != null && f.default !== ''
					? Number( f.default )
					: '';
		} else if ( f.type === 'select' ) {
			const opts = Array.isArray( f.options ) ? f.options : [];
			const first = opts[ 0 ];
			const fallback =
				f.default != null
					? String( f.default )
					: typeof first === 'string'
						? first
						: String( first?.value ?? '' );
			item[ f.key ] = fallback;
		} else if ( f.type === 'repeater' ) {
			item[ f.key ] = Array.isArray( f.default ) ? f.default : [];
		} else if ( f.type === 'icon' || f.type === 'dashicon' ) {
			item[ f.key ] =
				f.default != null ? String( f.default ) : 'dashicons:star-filled';
		} else {
			item[ f.key ] = f.default != null ? String( f.default ) : '';
		}
	} );
	if ( ! Object.keys( item ).length ) {
		item.title = '';
	}
	void index;
	return applySharedCollectionIcon( item, field, siblings );
}

/**
 * Min items for a collection repeater.
 *
 * @param {object} field
 * @return {number}
 */
export function collectionMinItems( field = {} ) {
	const n = Number( field.minItems );
	return Number.isFinite( n ) && n > 0 ? n : 1;
}

/**
 * Confirm copy for deleting a focused collection item from preview chrome / Fields.
 *
 * @param {object|null} field Collection repeater field
 * @return {{
 *   title: string,
 *   confirmLabel: string,
 *   fallbackName: string,
 *   description: (label: string) => string
 * }}
 */
export function collectionRemoveConfirmCopy( field = null ) {
	const itemType = String( field?.itemType || '' );
	const key = String( field?.key || '' );
	if ( itemType === 'tab' || key === 'tabs' ) {
		return {
			title: __( 'Delete this tab?', 'joc-blockora-morph' ),
			confirmLabel: __( 'Delete tab', 'joc-blockora-morph' ),
			fallbackName: __( 'This tab', 'joc-blockora-morph' ),
			description: ( label ) =>
				sprintf(
					/* translators: %s: tab title */
					__(
						'“%s” and its content will be permanently removed from this block.',
						'joc-blockora-morph'
					),
					label
				),
		};
	}
	if ( itemType === 'accordion' || key === 'items' ) {
		return {
			title: __( 'Delete this item?', 'joc-blockora-morph' ),
			confirmLabel: __( 'Delete item', 'joc-blockora-morph' ),
			fallbackName: __( 'This item', 'joc-blockora-morph' ),
			description: ( label ) =>
				sprintf(
					/* translators: %s: accordion item title */
					__(
						'“%s” and its content will be permanently removed from this block.',
						'joc-blockora-morph'
					),
					label
				),
		};
	}
	return {
		title: __( 'Delete this card?', 'joc-blockora-morph' ),
		confirmLabel: __( 'Delete card', 'joc-blockora-morph' ),
		fallbackName: __( 'This card', 'joc-blockora-morph' ),
		description: ( label ) =>
			sprintf(
				/* translators: %s: card title */
				__(
					'“%s” and its content will be permanently removed from this block.',
					'joc-blockora-morph'
				),
				label
			),
	};
}

/**
 * Confirm title for deleting a focused collection item.
 *
 * @param {object|null} field Collection repeater field
 * @return {string}
 */
export function collectionRemoveConfirmMessage( field = null ) {
	return collectionRemoveConfirmCopy( field ).title;
}

/**
 * Deep-clone a collection item and insert it after `index`.
 *
 * @param {object}               attributes Block attributes / default attrs.
 * @param {string}               collectionKey e.g. cards | items | tabs
 * @param {number}               index Source item index.
 * @return {{ patch: object, newIndex: number }|null}
 */
export function duplicateCollectionItemAt(
	attributes,
	collectionKey,
	index
) {
	const key = String( collectionKey || '' ).trim();
	if ( ! key ) {
		return null;
	}
	const list = Array.isArray( attributes?.[ key ] )
		? attributes[ key ]
		: null;
	const i = Number( index );
	if (
		! list ||
		! Number.isFinite( i ) ||
		i < 0 ||
		i >= list.length ||
		! list[ i ]
	) {
		return null;
	}
	let copy;
	try {
		copy = JSON.parse( JSON.stringify( list[ i ] ) );
	} catch ( e ) {
		return null;
	}
	const next = [ ...list ];
	next.splice( i + 1, 0, copy );
	return {
		newIndex: i + 1,
		patch: { [ key ]: next },
	};
}

/**
 * Move a collection item from `from` to `to`, remapping `activeTab` when present.
 * Items are deep-cloned so the new array never shares object identity with the
 * previous attribute snapshot (avoids WP/React merge weirdness looking like a copy).
 *
 * @param {object} attributes
 * @param {string} collectionKey
 * @param {number} from
 * @param {number} to
 * @return {{ patch: object, focusIndex: number }|null}
 */
export function moveCollectionItemAt(
	attributes,
	collectionKey,
	from,
	to
) {
	const key = String( collectionKey || '' ).trim();
	if ( ! key ) {
		return null;
	}
	const source = Array.isArray( attributes?.[ key ] )
		? attributes[ key ]
		: null;
	const fromIndex = Number( from );
	const toIndex = Number( to );
	if (
		! source ||
		source.length < 2 ||
		! Number.isFinite( fromIndex ) ||
		! Number.isFinite( toIndex ) ||
		fromIndex === toIndex ||
		fromIndex < 0 ||
		toIndex < 0 ||
		fromIndex >= source.length ||
		toIndex >= source.length
	) {
		return null;
	}
	let list;
	try {
		list = JSON.parse( JSON.stringify( source ) );
	} catch ( e ) {
		list = source.map( ( row ) =>
			row && typeof row === 'object' ? { ...row } : row
		);
	}
	const [ item ] = list.splice( fromIndex, 1 );
	list.splice( toIndex, 0, item );
	const patch = { [ key ]: list };
	const active = Number( attributes?.activeTab );
	if ( Number.isFinite( active ) ) {
		if ( active === fromIndex ) {
			patch.activeTab = toIndex;
		} else if (
			fromIndex < toIndex &&
			active > fromIndex &&
			active <= toIndex
		) {
			patch.activeTab = active - 1;
		} else if (
			fromIndex > toIndex &&
			active >= toIndex &&
			active < fromIndex
		) {
			patch.activeTab = active + 1;
		}
	}
	return { patch, focusIndex: toIndex };
}
