/**
 * Instance content attrs — My Block linked vs independent.
 *
 * Linked My Block placements (not customized) follow live `defaultAttrs`
 * for content, same as Design follows library cssVars.
 * Customize this instance snapshots content + styles onto the placement.
 */

import { overlayButtonsPlacementStyles } from './buttonStyleBaseline';
import { BLOCK_STYLE_SCOPE_KEY } from './styleScopeMemory';

/** Attribute keys that are never treated as My Block content fields. */
const NON_CONTENT_KEYS = new Set( [
	'cssVars',
	'styleScopeChoices',
	'librarySaveRev',
	'allowedPostTypes',
	'align',
	'className',
	'anchor',
] );

/**
 * @param {object|null|undefined} styleScopeChoices
 * @return {boolean}
 */
function isIndependentScope( styleScopeChoices ) {
	return (
		styleScopeChoices &&
		typeof styleScopeChoices === 'object' &&
		! Array.isArray( styleScopeChoices ) &&
		styleScopeChoices[ BLOCK_STYLE_SCOPE_KEY ] === 'instance'
	);
}

/**
 * @param {object} meta
 * @return {string[]}
 */
export function contentFieldKeysFromMeta( meta = {} ) {
	const keys = [];
	const seen = new Set();
	const push = ( key ) => {
		if ( ! key || NON_CONTENT_KEYS.has( key ) || seen.has( key ) ) {
			return;
		}
		seen.add( key );
		keys.push( key );
	};

	( Array.isArray( meta.fieldSchema ) ? meta.fieldSchema : [] ).forEach(
		( field ) => {
			push( field?.key );
		}
	);
	push( 'elementsVisibility' );
	return keys;
}

/**
 * Whether this placement still follows the My Block for content.
 * Library catalog blocks are never content-linked.
 *
 * @param {object} meta
 * @param {object} attributes
 * @return {boolean}
 */
export function isContentLinkedToMyBlock( meta = {}, attributes = {} ) {
	if ( meta?.isLibraryBlock ) {
		return false;
	}
	return ! isIndependentScope( attributes?.styleScopeChoices );
}

/**
 * Pick content values from a source map for schema keys.
 *
 * @param {object} meta
 * @param {object} source
 * @return {object}
 */
export function pickContentAttrs( meta = {}, source = {} ) {
	const out = {};
	const src = source && typeof source === 'object' ? source : {};
	contentFieldKeysFromMeta( meta ).forEach( ( key ) => {
		if ( src[ key ] !== undefined ) {
			out[ key ] = src[ key ];
		}
	} );
	return out;
}

/**
 * Snapshot content onto a placement when forking to an independent instance.
 * Linked → use live My Block defaults. Already independent (migration) → keep attrs.
 *
 * @param {object} meta
 * @param {object} attributes
 * @return {object}
 */
export function snapshotContentAttrsForFork( meta = {}, attributes = {} ) {
	const defaults =
		meta.defaultAttrs && typeof meta.defaultAttrs === 'object'
			? meta.defaultAttrs
			: {};
	if ( isIndependentScope( attributes?.styleScopeChoices ) ) {
		const fromAttrs = pickContentAttrs( meta, attributes );
		const fromDefaults = pickContentAttrs( meta, defaults );
		return { ...fromDefaults, ...fromAttrs };
	}
	return pickContentAttrs( meta, defaults );
}

/**
 * Build markup attrs for preview / interpolate.
 * Linked My Blocks use live defaultAttrs; independent use placement attributes.
 *
 * @param {object} meta
 * @param {object} attributes
 * @return {object}
 */
export function resolveInstanceContentAttrs( meta = {}, attributes = {} ) {
	const schema = Array.isArray( meta.fieldSchema ) ? meta.fieldSchema : [];
	const defaults =
		meta.defaultAttrs && typeof meta.defaultAttrs === 'object'
			? meta.defaultAttrs
			: {};
	const linked = isContentLinkedToMyBlock( meta, attributes );
	const out = {};

	schema.forEach( ( field ) => {
		const key = field?.key;
		if ( ! key || NON_CONTENT_KEYS.has( key ) ) {
			return;
		}
		if ( linked ) {
			if ( defaults[ key ] === undefined ) {
				return;
			}
			// Style reference + button Design live on the placement even while
			// label/URL content still follows the My Block.
			if ( key === 'buttons' ) {
				out[ key ] = overlayButtonsPlacementStyles(
					defaults[ key ],
					attributes[ key ]
				);
				return;
			}
			if ( key === 'slides' && Array.isArray( defaults.slides ) ) {
				const placementSlides = Array.isArray( attributes.slides )
					? attributes.slides
					: [];
				out[ key ] = defaults.slides.map( ( slide, index ) => {
					const placement = placementSlides[ index ];
					if ( ! placement || typeof placement !== 'object' ) {
						return slide;
					}
					return {
						...slide,
						buttons: overlayButtonsPlacementStyles(
							slide?.buttons,
							placement.buttons
						),
					};
				} );
				return;
			}
			if (
				Array.isArray( defaults[ key ] ) &&
				field?.itemType &&
				field.itemType !== 'slide'
			) {
				const placementItems = Array.isArray( attributes[ key ] )
					? attributes[ key ]
					: [];
				out[ key ] = defaults[ key ].map( ( item, index ) => {
					const placement = placementItems[ index ];
					if (
						! placement ||
						typeof placement !== 'object' ||
						! Array.isArray( item?.buttons )
					) {
						return item;
					}
					return {
						...item,
						buttons: overlayButtonsPlacementStyles(
							item.buttons,
							placement.buttons
						),
					};
				} );
				return;
			}
			out[ key ] = defaults[ key ];
			return;
		}
		if ( attributes[ key ] !== undefined ) {
			out[ key ] = attributes[ key ];
			return;
		}
		if ( defaults[ key ] !== undefined ) {
			out[ key ] = defaults[ key ];
		}
	} );

	if ( linked ) {
		if ( defaults.elementsVisibility !== undefined ) {
			out.elementsVisibility = defaults.elementsVisibility;
		}
	} else if ( attributes.elementsVisibility !== undefined ) {
		out.elementsVisibility = attributes.elementsVisibility;
	} else if ( defaults.elementsVisibility !== undefined ) {
		out.elementsVisibility = defaults.elementsVisibility;
	}

	return out;
}
