/**
 * Fork a block placement into a fully independent instance:
 * snapshot merged CSS vars + content attrs onto the placement.
 * Button library source links are left as authored — they stay wired.
 */
import { merge, normalize } from '../cssVars';
import { snapshotContentAttrsForFork } from './instanceContentAttrs';
import { BLOCK_STYLE_SCOPE_KEY } from './styleScopeMemory';

/** Bumped when the independent-instance fork format changes. */
export const INSTANCE_SCOPE_FORK_VERSION = 3;

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

/**
 * @param {object|null|undefined} styleScopeChoices
 * @return {boolean}
 */
export function needsIndependentInstanceFork( styleScopeChoices ) {
	if ( ! isIndependentInstanceScope( styleScopeChoices ) ) {
		return false;
	}
	return Number( styleScopeChoices.__forkVersion ) !== INSTANCE_SCOPE_FORK_VERSION;
}

/**
 * Whether a setAttributes patch is allowed while still linked to My Blocks.
 * Only the Customize / independent-instance fork may write (must include cssVars).
 *
 * @param {object|null|undefined} patch
 * @return {boolean}
 */
export function isAllowedLinkedPlacementPatch( patch ) {
	if ( ! patch || typeof patch !== 'object' || Array.isArray( patch ) ) {
		return false;
	}
	const keys = Object.keys( patch );
	// Identity stamp for My Blocks row — safe while linked.
	if ( keys.length === 1 && keys[ 0 ] === 'userBlockId' ) {
		return true;
	}
	const scope = patch.styleScopeChoices;
	if (
		! scope ||
		typeof scope !== 'object' ||
		Array.isArray( scope ) ||
		scope[ BLOCK_STYLE_SCOPE_KEY ] !== 'instance'
	) {
		return false;
	}
	// Require a real fork snapshot — never bare styleScopeChoices writes
	// (those dirty the post without unlocking content correctly).
	return Object.prototype.hasOwnProperty.call( patch, 'cssVars' );
}

/**
 * Wrap setAttributes so linked My Block placements cannot persist content/style edits.
 *
 * @param {Function} setAttributes
 * @param {object}   options
 * @param {Function} [options.getMeta]
 * @param {Function} [options.getAttributes]
 * @param {boolean|Function} [options.enabled]
 * @return {Function}
 */
export function createLinkedPlacementSetAttributes(
	setAttributes,
	{ getMeta, getAttributes, enabled } = {}
) {
	if ( typeof setAttributes !== 'function' ) {
		return () => {};
	}
	return ( patch ) => {
		const meta =
			typeof getMeta === 'function' ? getMeta() || {} : getMeta || {};
		const attrs =
			typeof getAttributes === 'function'
				? getAttributes() || {}
				: getAttributes || {};
		const gateOn =
			typeof enabled === 'function'
				? Boolean( enabled() )
				: enabled == null
				? ! meta.isLibraryBlock
				: Boolean( enabled );
		if (
			! gateOn ||
			meta.isLibraryBlock ||
			isIndependentInstanceScope( attrs.styleScopeChoices )
		) {
			setAttributes( patch );
			return;
		}
		if ( isAllowedLinkedPlacementPatch( patch ) ) {
			setAttributes( patch );
		}
	};
}

/**
 * Build attribute patch that makes this placement’s content + styles independent
 * of the My Block. Does not change button Source / library wiring.
 *
 * @param {object} attributes
 * @param {object} libraryCssVars
 * @param {object} [meta] Block meta (fieldSchema + defaultAttrs) for content snapshot
 * @return {object}
 */
export function buildIndependentInstancePatch(
	attributes = {},
	libraryCssVars = {},
	meta = {}
) {
	const cssVars = merge( libraryCssVars || {}, attributes.cssVars || {} );
	const content = snapshotContentAttrsForFork( meta, attributes );
	return {
		...content,
		cssVars: normalize( cssVars ),
		styleScopeChoices: {
			[ BLOCK_STYLE_SCOPE_KEY ]: 'instance',
			__forkVersion: INSTANCE_SCOPE_FORK_VERSION,
		},
	};
}

/**
 * Effective cssVars for preview/controls when the placement is independent.
 * Library-linked placements ignore baked full-library attribute snapshots.
 *
 * @param {object} attributes
 * @param {object} libraryCssVars
 * @param {object|null} [libraryBaseline]
 * @param {Function} [mergeLibraryPreviewFn]
 * @return {object}
 */
export function resolvePlacementCssVars(
	attributes,
	libraryCssVars,
	libraryBaseline = null,
	mergeLibraryPreviewFn = null
) {
	if ( isIndependentInstanceScope( attributes?.styleScopeChoices ) ) {
		return normalize( attributes?.cssVars || {} );
	}
	if ( typeof mergeLibraryPreviewFn === 'function' ) {
		return mergeLibraryPreviewFn(
			libraryCssVars || {},
			attributes?.cssVars || {},
			libraryBaseline != null ? libraryBaseline : libraryCssVars || {}
		);
	}
	return merge( libraryCssVars || {}, attributes?.cssVars || {} );
}
