import clsx from 'clsx';
import { __ } from '@wordpress/i18n';
import { useSelect } from '@wordpress/data';
import { useState, useRef } from '@wordpress/element';
import {
	BlockControls,
	BlockVerticalAlignmentControl,
	useInnerBlocksProps,
	InspectorControls,
	useBlockProps,
	__experimentalImageURLInputUI as ImageURLInputUI,
	store as blockEditorStore,
	useBlockEditingMode,
	privateApis as blockEditorPrivateApis,
} from '@wordpress/block-editor';
import {
	RangeControl,
	TextareaControl as WCTextareaControl,
	ToggleControl,
	ToolbarButton,
	ExternalLink,
	FocalPointPicker,
	__experimentalToolsPanel as ToolsPanel,
	__experimentalToolsPanelItem as ToolsPanelItem,
} from '@wordpress/components';
import { isBlobURL, getBlobTypeByURL } from '@wordpress/blob';
import { pullLeft, pullRight } from '@wordpress/icons';
import { useEntityProp, store as coreStore } from '@wordpress/core-data';
import MediaContainer from './media-container';
import {
	DEFAULT_MEDIA_SIZE_SLUG,
	WIDTH_CONSTRAINT_PERCENTAGE,
	LINK_DESTINATION_NONE,
	LINK_DESTINATION_MEDIA,
	LINK_DESTINATION_ATTACHMENT,
} from './constants';
import { unlock } from '../lock-unlock';
import { useToolsPanelDropdownMenuProps } from '../utils/hooks';

const { ResolutionTool } = unlock( blockEditorPrivateApis );

// this limits the resize to a safe zone to avoid making broken layouts
const applyWidthConstraints = ( width ) =>
	Math.max(
		WIDTH_CONSTRAINT_PERCENTAGE,
		Math.min( width, 100 - WIDTH_CONSTRAINT_PERCENTAGE )
	);

function getImageSourceUrlBySizeSlug( image, slug ) {
	return image?.media_details?.sizes?.[ slug ]?.source_url;
}

function attributesFromMedia( {
	attributes: { linkDestination, href },
	setAttributes,
} ) {
	return ( media ) => {
		if ( ! media || ! media.url ) {
			setAttributes( {
				mediaAlt: undefined,
				mediaId: undefined,
				mediaType: undefined,
				mediaUrl: undefined,
				mediaLink: undefined,
				href: undefined,
				focalPoint: undefined,
				useFeaturedImage: false,
			} );
			return;
		}

		if ( isBlobURL( media.url ) ) {
			media.type = getBlobTypeByURL( media.url );
		}

		let mediaType;
		let src;
		// For media selections originated from a file upload.
		if ( media.media_type ) {
			if ( media.media_type === 'image' ) {
				mediaType = 'image';
			} else {
				// only images and videos are accepted so if the media_type is not an image we can assume it is a video.
				// video contain the media type of 'file' in the object returned from the rest api.
				mediaType = 'video';
			}
		} else {
			// For media selections originated from existing files in the media library.
			mediaType = media.type;
		}

		if ( mediaType === 'image' ) {
			// Try the "large" size URL, falling back to the "full" size URL below.
			src =
				media.sizes?.large?.url ||
				media.media_details?.sizes?.large?.source_url;
		}

		let newLinkDestination = linkDestination;
		let newHref = href;

		// Only apply default link behavior for images (not videos).
		if ( mediaType === 'image' ) {
			// Check if default link setting should be used.
			if ( ! newLinkDestination ) {
				// Use the WordPress option to determine the proper default.
				// The constants used in Gutenberg do not match WP options so a little more complicated than ideal.
				switch (
					window?.wp?.media?.view?.settings?.defaultProps?.link ||
					LINK_DESTINATION_NONE
				) {
					case 'file':
					case LINK_DESTINATION_MEDIA:
						newLinkDestination = LINK_DESTINATION_MEDIA;
						break;
					case 'post':
					case LINK_DESTINATION_ATTACHMENT:
						newLinkDestination = LINK_DESTINATION_ATTACHMENT;
						break;
					case LINK_DESTINATION_NONE:
					default:
						newLinkDestination = LINK_DESTINATION_NONE;
						break;
				}
			}

			// Set href based on linkDestination.
			switch ( newLinkDestination ) {
				case LINK_DESTINATION_MEDIA:
					newHref = media.url;
					break;
				case LINK_DESTINATION_ATTACHMENT:
					newHref = media.link;
					break;
			}
		}

		setAttributes( {
			mediaAlt: media.alt,
			mediaId: media.id,
			mediaType,
			mediaUrl: src || media.url,
			mediaLink: media.link || undefined,
			href: newHref,
			linkDestination: newLinkDestination,
			focalPoint: undefined,
			useFeaturedImage: false,
		} );
	};
}

function MediaTextResolutionTool( { image, value, onChange } ) {
	const { imageSizes } = useSelect( ( select ) => {
		const { getSettings } = select( blockEditorStore );
		return {
			imageSizes: getSettings().imageSizes,
		};
	}, [] );

	if ( ! imageSizes?.length ) {
		return null;
	}

	const imageSizeOptions = imageSizes
		.filter( ( { slug } ) => getImageSourceUrlBySizeSlug( image, slug ) )
		.map( ( { name, slug } ) => ( { value: slug, label: name } ) );

	return (
		<ResolutionTool
			value={ value }
			defaultValue={ DEFAULT_MEDIA_SIZE_SLUG }
			options={ imageSizeOptions }
			onChange={ onChange }
		/>
	);
}

function MediaTextEdit( {
	attributes,
	isSelected,
	setAttributes,
	context: { postId, postType },
} ) {
	const {
		focalPoint,
		href,
		imageFill,
		isStackedOnMobile,
		linkClass,
		linkDestination,
		linkTarget,
		mediaAlt,
		mediaId,
		mediaPosition,
		mediaType,
		mediaUrl,
		mediaWidth,
		mediaSizeSlug,
		rel,
		verticalAlignment,
		allowedBlocks,
		useFeaturedImage,
	} = attributes;

	const [ featuredImage ] = useEntityProp(
		'postType',
		postType,
		'featured_media',
		postId
	);

	const { featuredImageMedia } = useSelect(
		( select ) => {
			return {
				featuredImageMedia:
					featuredImage && useFeaturedImage
						? select( coreStore ).getEntityRecord(
								'postType',
								'attachment',
								featuredImage,
								{
									context: 'view',
								}
						  )
						: undefined,
			};
		},
		[ featuredImage, useFeaturedImage ]
	);

	const { image } = useSelect(
		( select ) => {
			return {
				image:
					mediaId && isSelected
						? select( coreStore ).getEntityRecord(
								'postType',
								'attachment',
								mediaId,
								{
									context: 'view',
								}
						  )
						: null,
			};
		},
		[ isSelected, mediaId ]
	);

	const featuredImageURL = useFeaturedImage
		? featuredImageMedia?.source_url
		: '';
	const featuredImageAlt = useFeaturedImage
		? featuredImageMedia?.alt_text
		: '';

	const toggleUseFeaturedImage = () => {
		setAttributes( {
			imageFill: false,
			mediaType: 'image',
			mediaId: undefined,
			mediaUrl: undefined,
			mediaAlt: undefined,
			mediaLink: undefined,
			linkDestination: undefined,
			linkTarget: undefined,
			linkClass: undefined,
			rel: undefined,
			href: undefined,
			useFeaturedImage: ! useFeaturedImage,
		} );
	};

	const refMedia = useRef();
	const imperativeFocalPointPreview = ( value ) => {
		const { style } = refMedia.current;
		const { x, y } = value;
		style.objectPosition = `${ x * 100 }% ${ y * 100 }%`;
	};

	const [ temporaryMediaWidth, setTemporaryMediaWidth ] = useState( null );

	const onSelectMedia = attributesFromMedia( { attributes, setAttributes } );

	const onSetHref = ( props ) => {
		setAttributes( props );
	};

	const onWidthChange = ( width ) => {
		setTemporaryMediaWidth( applyWidthConstraints( width ) );
	};
	const commitWidthChange = ( width ) => {
		setAttributes( {
			mediaWidth: applyWidthConstraints( width ),
		} );
		setTemporaryMediaWidth( null );
	};

	const classNames = clsx( {
		'has-media-on-the-right': 'right' === mediaPosition,
		'is-selected': isSelected,
		'is-stacked-on-mobile': isStackedOnMobile,
		[ `is-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment,
		'is-image-fill-element': imageFill,
	} );
	const widthString = `${ temporaryMediaWidth || mediaWidth }%`;
	const gridTemplateColumns =
		'right' === mediaPosition
			? `1fr ${ widthString }`
			: `${ widthString } 1fr`;
	const style = {
		gridTemplateColumns,
		msGridColumns: gridTemplateColumns,
	};
	const onMediaAltChange = ( newMediaAlt ) => {
		setAttributes( { mediaAlt: newMediaAlt } );
	};
	const onVerticalAlignmentChange = ( alignment ) => {
		setAttributes( { verticalAlignment: alignment } );
	};
	const updateImage = ( newMediaSizeSlug ) => {
		const newUrl = getImageSourceUrlBySizeSlug( image, newMediaSizeSlug );

		if ( ! newUrl ) {
			return null;
		}

		setAttributes( {
			mediaUrl: newUrl,
			mediaSizeSlug: newMediaSizeSlug,
		} );
	};
	const dropdownMenuProps = useToolsPanelDropdownMenuProps();

	const mediaTextGeneralSettings = (
		<ToolsPanel
			label={ __( 'Settings' ) }
			resetAll={ () => {
				setAttributes( {
					isStackedOnMobile: true,
					imageFill: false,
					mediaAlt: '',
					focalPoint: undefined,
					mediaWidth: 50,
				} );
				updateImage( DEFAULT_MEDIA_SIZE_SLUG );
			} }
			dropdownMenuProps={ dropdownMenuProps }
		>
			<ToolsPanelItem
				label={ __( 'Media width' ) }
				isShownByDefault
				hasValue={ () => mediaWidth !== 50 }
				onDeselect={ () => setAttributes( { mediaWidth: 50 } ) }
			>
				<RangeControl
					label={ __( 'Media width' ) }
					value={ temporaryMediaWidth || mediaWidth }
					onChange={ commitWidthChange }
					min={ WIDTH_CONSTRAINT_PERCENTAGE }
					max={ 100 - WIDTH_CONSTRAINT_PERCENTAGE }
				/>
			</ToolsPanelItem>
			<ToolsPanelItem
				label={ __( 'Stack on mobile' ) }
				isShownByDefault
				hasValue={ () => ! isStackedOnMobile }
				onDeselect={ () =>
					setAttributes( { isStackedOnMobile: true } )
				}
			>
				<ToggleControl
					label={ __( 'Stack on mobile' ) }
					checked={ isStackedOnMobile }
					onChange={ () =>
						setAttributes( {
							isStackedOnMobile: ! isStackedOnMobile,
						} )
					}
				/>
			</ToolsPanelItem>
			{ mediaType === 'image' && (
				<ToolsPanelItem
					label={ __( 'Crop image to fill' ) }
					isShownByDefault
					hasValue={ () => !! imageFill }
					onDeselect={ () => setAttributes( { imageFill: false } ) }
				>
					<ToggleControl
						label={ __( 'Crop image to fill' ) }
						checked={ !! imageFill }
						onChange={ () =>
							setAttributes( {
								imageFill: ! imageFill,
							} )
						}
					/>
				</ToolsPanelItem>
			) }
			{ imageFill &&
				( mediaUrl || featuredImageURL ) &&
				mediaType === 'image' && (
					<ToolsPanelItem
						label={ __( 'Focal point' ) }
						isShownByDefault
						hasValue={ () => !! focalPoint }
						onDeselect={ () =>
							setAttributes( { focalPoint: undefined } )
						}
					>
						<FocalPointPicker
							label={ __( 'Focal point' ) }
							url={
								useFeaturedImage && featuredImageURL
									? featuredImageURL
									: mediaUrl
							}
							value={ focalPoint }
							onChange={ ( value ) =>
								setAttributes( { focalPoint: value } )
							}
							onDragStart={ imperativeFocalPointPreview }
							onDrag={ imperativeFocalPointPreview }
						/>
					</ToolsPanelItem>
				) }
			{ mediaType === 'image' && mediaUrl && ! useFeaturedImage && (
				<ToolsPanelItem
					label={ __( 'Alternative text' ) }
					isShownByDefault
					hasValue={ () => !! mediaAlt }
					onDeselect={ () => setAttributes( { mediaAlt: '' } ) }
				>
					<WCTextareaControl
						label={ __( 'Alternative text' ) }
						value={ mediaAlt }
						onChange={ onMediaAltChange }
						help={
							<>
								<ExternalLink
									href={
										// translators: Localized tutorial, if one exists. W3C Web Accessibility Initiative link has list of existing translations.
										__(
											'https://www.w3.org/WAI/tutorials/images/decision-tree/'
										)
									}
								>
									{ __(
										'Describe the purpose of the image.'
									) }
								</ExternalLink>
								<br />
								{ __( 'Leave empty if decorative.' ) }
							</>
						}
					/>
				</ToolsPanelItem>
			) }
			{ mediaType === 'image' && ! useFeaturedImage && (
				<MediaTextResolutionTool
					image={ image }
					value={ mediaSizeSlug }
					onChange={ updateImage }
				/>
			) }
		</ToolsPanel>
	);

	const blockProps = useBlockProps( {
		className: classNames,
		style,
	} );

	const innerBlocksProps = useInnerBlocksProps(
		{ className: 'wp-block-media-text__content' },
		{ allowedBlocks }
	);

	const blockEditingMode = useBlockEditingMode();

	return (
		<>
			<InspectorControls>{ mediaTextGeneralSettings }</InspectorControls>
			<BlockControls group="block">
				{ blockEditingMode === 'default' && (
					<>
						<BlockVerticalAlignmentControl
							onChange={ onVerticalAlignmentChange }
							value={ verticalAlignment }
							label={ __( 'Align media and text vertically' ) }
						/>
						<ToolbarButton
							icon={ pullLeft }
							title={ __( 'Show media on left' ) }
							isActive={ mediaPosition === 'left' }
							onClick={ () =>
								setAttributes( { mediaPosition: 'left' } )
							}
						/>
						<ToolbarButton
							icon={ pullRight }
							title={ __( 'Show media on right' ) }
							isActive={ mediaPosition === 'right' }
							onClick={ () =>
								setAttributes( { mediaPosition: 'right' } )
							}
						/>
					</>
				) }

				{ mediaType === 'image' && ! useFeaturedImage && (
					<ImageURLInputUI
						url={ href || '' }
						onChangeUrl={ onSetHref }
						linkDestination={ linkDestination }
						mediaType={ mediaType }
						mediaUrl={ image && image.source_url }
						mediaLink={ image && image.link }
						linkTarget={ linkTarget }
						linkClass={ linkClass }
						rel={ rel }
					/>
				) }
			</BlockControls>
			<div { ...blockProps }>
				{ mediaPosition === 'right' && <div { ...innerBlocksProps } /> }
				<MediaContainer
					className="wp-block-media-text__media"
					onSelectMedia={ onSelectMedia }
					onWidthChange={ onWidthChange }
					commitWidthChange={ commitWidthChange }
					refMedia={ refMedia }
					enableResize={ blockEditingMode === 'default' }
					toggleUseFeaturedImage={ toggleUseFeaturedImage }
					{ ...{
						focalPoint,
						imageFill,
						isSelected,
						isStackedOnMobile,
						mediaAlt,
						mediaId,
						mediaPosition,
						mediaType,
						mediaUrl,
						mediaWidth,
						useFeaturedImage,
						featuredImageURL,
						featuredImageAlt,
					} }
				/>
				{ mediaPosition !== 'right' && <div { ...innerBlocksProps } /> }
			</div>
		</>
	);
}

export default MediaTextEdit;
