/**
 * Internal dependencies
 */
import { ImageFile } from '../../image-file';
import { getFileBasename } from '../../utils';
import type { ImageSizeCrop, QueueItemId } from '../types';

/**
 * Cached dynamic import promise for @wordpress/vips/worker.
 *
 * The module contains ~10MB of inlined WASM code. By using a dynamic import,
 * the WASM is only loaded when vips functions are actually called at image
 * processing time, rather than at module parse time.
 *
 * The promise is cached so the module is only resolved once.
 */
let vipsModulePromise:
	| Promise< typeof import('@wordpress/vips/worker') >
	| undefined;

/**
 * The resolved module reference, available synchronously after the first
 * load completes. Used by terminateVipsWorker() and vipsCancelOperations().
 */
let vipsModule: typeof import('@wordpress/vips/worker') | undefined;

/**
 * Lazily loads and caches the @wordpress/vips/worker module.
 *
 * @return The vips worker module.
 */
function loadVipsModule(): Promise< typeof import('@wordpress/vips/worker') > {
	if ( ! vipsModulePromise ) {
		vipsModulePromise = import( '@wordpress/vips/worker' ).then(
			( mod ) => {
				vipsModule = mod;
				return mod;
			}
		);
	}
	return vipsModulePromise;
}

/**
 * Converts an image to a different format using vips in a web worker.
 *
 * @param id         Queue item ID.
 * @param file       File object.
 * @param type       Output mime type.
 * @param quality    Desired quality (0-1).
 * @param interlaced Whether to use interlaced/progressive mode.
 * @return Converted file.
 */
export async function vipsConvertImageFormat(
	id: QueueItemId,
	file: File,
	type:
		| 'image/jpeg'
		| 'image/png'
		| 'image/webp'
		| 'image/avif'
		| 'image/gif',
	quality: number,
	interlaced?: boolean
) {
	const { vipsConvertImageFormat: convertImageFormat } =
		await loadVipsModule();
	const buffer = await convertImageFormat(
		id,
		await file.arrayBuffer(),
		file.type,
		type,
		quality,
		interlaced
	);
	const ext = type.split( '/' )[ 1 ];
	const fileName = `${ getFileBasename( file.name ) }.${ ext }`;
	return new File( [ new Blob( [ buffer as ArrayBuffer ] ) ], fileName, {
		type,
	} );
}

/**
 * Compresses an image using vips in a web worker.
 *
 * @param id         Queue item ID.
 * @param file       File object.
 * @param quality    Desired quality (0-1).
 * @param interlaced Whether to use interlaced/progressive mode.
 * @return Compressed file.
 */
export async function vipsCompressImage(
	id: QueueItemId,
	file: File,
	quality: number,
	interlaced?: boolean
) {
	const { vipsCompressImage: compressImage } = await loadVipsModule();
	const buffer = await compressImage(
		id,
		await file.arrayBuffer(),
		file.type,
		quality,
		interlaced
	);
	return new File(
		[ new Blob( [ buffer as ArrayBuffer ], { type: file.type } ) ],
		file.name,
		{ type: file.type }
	);
}

/**
 * Checks whether an image has transparency using vips in a web worker.
 *
 * @param url Image URL.
 * @return Whether the image has transparency.
 */
export async function vipsHasTransparency( url: string ) {
	const { vipsHasTransparency: hasTransparency } = await loadVipsModule();
	const response = await fetch( url );
	if ( ! response.ok ) {
		throw new Error( `Failed to fetch image: ${ response.status }` );
	}
	return hasTransparency( await response.arrayBuffer() );
}

/**
 * Probes a JPEG buffer for UltraHDR (ISO 21496-1 gain map) support using vips
 * in a web worker.
 *
 * @param buffer Image buffer to probe.
 * @return UltraHDR info if the buffer is a valid UltraHDR JPEG, otherwise null.
 */
export async function vipsGetUltraHdrInfo( buffer: ArrayBuffer ) {
	const { vipsGetUltraHdrInfo: getUltraHdrInfo } = await loadVipsModule();
	return getUltraHdrInfo( buffer );
}

/**
 * Resizes an image using vips in a web worker.
 *
 * UltraHDR JPEGs are auto-detected by libvips and their gain map is
 * preserved through the resize.
 *
 * @param id           Queue item ID.
 * @param file         File object.
 * @param resize       Resize options (width, height, crop).
 * @param smartCrop    Whether to use smart cropping (saliency-aware).
 * @param addSuffix    Whether to add dimension suffix to filename.
 * @param signal       Optional abort signal to cancel the operation.
 * @param scaledSuffix Whether to add '-scaled' suffix instead of dimensions (for big image threshold).
 * @param quality      Desired quality (0-1). Defaults to 0.82.
 * @return Resized ImageFile with dimension metadata.
 */
export async function vipsResizeImage(
	id: QueueItemId,
	file: File,
	resize: ImageSizeCrop,
	smartCrop: boolean,
	addSuffix: boolean,
	signal?: AbortSignal,
	scaledSuffix?: boolean,
	quality?: number
) {
	if ( signal?.aborted ) {
		throw new Error( 'Operation aborted' );
	}

	const { vipsResizeImage: resizeImage } = await loadVipsModule();
	const { buffer, width, height, originalWidth, originalHeight } =
		await resizeImage(
			id,
			await file.arrayBuffer(),
			file.type,
			resize,
			smartCrop,
			quality
		);

	let fileName = file.name;
	const wasResized = originalWidth > width || originalHeight > height;

	if ( wasResized ) {
		const basename = getFileBasename( file.name );
		if ( scaledSuffix ) {
			// Add '-scaled' suffix for big image threshold resizing.
			// This matches WordPress core's behavior in wp_create_image_subsizes().
			fileName = file.name.replace( basename, `${ basename }-scaled` );
		} else if ( addSuffix ) {
			// Add dimension suffix for thumbnails.
			fileName = file.name.replace(
				basename,
				`${ basename }-${ width }x${ height }`
			);
		}
	}

	const resultFile = new ImageFile(
		new File(
			[ new Blob( [ buffer as ArrayBuffer ], { type: file.type } ) ],
			fileName,
			{
				type: file.type,
			}
		),
		width,
		height,
		originalWidth,
		originalHeight
	);

	return resultFile;
}

/**
 * Rotates an image based on EXIF orientation using vips in a web worker.
 *
 * This applies the correct rotation/flip transformation based on the EXIF
 * orientation value (1-8), and adds a '-rotated' suffix to the filename.
 * This matches WordPress core's behavior when rotating images based on EXIF.
 *
 * @param id          Queue item ID.
 * @param file        File object.
 * @param orientation EXIF orientation value (1-8).
 * @param signal      Optional abort signal to cancel the operation.
 * @return Rotated ImageFile with updated dimensions.
 */
export async function vipsRotateImage(
	id: QueueItemId,
	file: File,
	orientation: number,
	signal?: AbortSignal
) {
	if ( signal?.aborted ) {
		throw new Error( 'Operation aborted' );
	}

	// If orientation is 1 (normal), no rotation needed.
	if ( orientation === 1 ) {
		return file;
	}

	const { vipsRotateImage: rotateImage } = await loadVipsModule();
	const { buffer, width, height } = await rotateImage(
		id,
		await file.arrayBuffer(),
		file.type,
		orientation
	);

	// Add '-rotated' suffix to filename, matching WordPress core behavior.
	const basename = getFileBasename( file.name );
	const fileName = file.name.replace( basename, `${ basename }-rotated` );

	const resultFile = new ImageFile(
		new File(
			[ new Blob( [ buffer as ArrayBuffer ], { type: file.type } ) ],
			fileName,
			{
				type: file.type,
			}
		),
		width,
		height
	);

	return resultFile;
}

/**
 * Cancels all ongoing image operations for the given item.
 *
 * If the vips module has not been loaded yet, there can be no active
 * operations to cancel.
 *
 * @param id Queue item ID to cancel operations for.
 * @return Whether any operation was cancelled.
 */
export async function vipsCancelOperations( id: QueueItemId ) {
	if ( ! vipsModule ) {
		return false;
	}
	return vipsModule.vipsCancelOperations( id );
}

/**
 * Terminates the vips worker if it has been loaded.
 *
 * If the vips module has not been loaded yet (i.e., no image processing
 * has occurred), this is a no-op since there is no worker to terminate.
 *
 * The worker itself is recreated lazily by `getWorkerAPI()` inside
 * `@wordpress/vips/worker` on the next vips call — the module reference
 * cached here can keep pointing at the same module since re-importing
 * returns the same instance from the JS module cache.
 */
export function terminateVipsWorker(): void {
	if ( vipsModule ) {
		vipsModule.terminateVipsWorker();
	}
}

/**
 * Tracks the number of completed vips image processing operations across
 * both the success path (`finishOperation`) and the failure path
 * (`cancelItem`). Used to periodically recycle the WASM worker to reclaim
 * memory, since WASM linear memory can only grow and never shrink.
 */
let completedVipsOperations = 0;

/**
 * Maximum number of vips operations before recycling the worker.
 * Each operation can consume 50-100MB+ of WASM memory for large images.
 */
const MAX_VIPS_OPS_BEFORE_RECYCLE = 50;

/**
 * Records that a vips operation has completed and recycles the worker if
 * the threshold has been reached and no other vips operations are in
 * flight. Call this from both success and failure paths so that a burst
 * of failures can't bypass the recycle budget.
 *
 * @param activeImageProcessingCount Number of vips operations currently
 *                                   in flight. Recycling is deferred while
 *                                   any are running so an in-flight worker
 *                                   isn't killed mid-operation.
 */
export function maybeRecycleVipsWorker(
	activeImageProcessingCount: number
): void {
	completedVipsOperations++;

	if (
		completedVipsOperations >= MAX_VIPS_OPS_BEFORE_RECYCLE &&
		activeImageProcessingCount === 0
	) {
		terminateVipsWorker();
		completedVipsOperations = 0;
	}
}
