import { useCallback, useEffect, useMemo, useRef, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import {
	normalize,
	styleableFieldsInOrder,
} from '../../cssVars';
import { api } from '../api';
import { renderPreview } from '../components/PreviewModal';
import PreviewFrame from '../components/PreviewFrame';
import WysiwygField from '../components/WysiwygField';
import TemplateUpdateModal from '../components/TemplateUpdateModal';
import BlockInspectorPanel from '../../shared/BlockInspectorPanel';
import { scrollInspectorToTop } from '../../shared/inspectorScroll';
import useUnsavedChanges from '../hooks/useUnsavedChanges';
import useSaveShortcut from '../hooks/useSaveShortcut';
import FloatingToast from '../components/ui/FloatingToast';
import Notice from '../components/ui/Notice';
import { brandLogoUrl } from '../../shared/brandLogo';
import { useButtonLibrary } from '../../shared/buttonLibrary';
import BrandLoader from '../components/BrandLoader';

function snapshotContent( state ) {
	return JSON.stringify( {
		name: state.name,
		attrs: state.attrs,
		cssVars: state.cssVars,
	} );
}

function setAttrIfChanged( key, next ) {
	return ( prev ) => {
		if ( prev[ key ] === next ) {
			return prev;
		}
		if (
			typeof next === 'object' &&
			next !== null &&
			JSON.stringify( prev[ key ] ?? null ) === JSON.stringify( next )
		) {
			return prev;
		}
		return { ...prev, [ key ]: next };
	};
}

/** Match Gutenberg wide inspector. */
const CONTENT_SIDEBAR_WIDTH = 420;

export default function ContentEditor( { route, onBack } ) {
	const buttonLibrary = useButtonLibrary( {
		refreshOnMount: true,
		refreshOnFocus: true,
	} );
	const id = parseInt( route.split( '/' )[ 1 ], 10 );
	const [ item, setItem ] = useState( null );
	const [ attrs, setAttrs ] = useState( {} );
	const [ cssVars, setCssVars ] = useState( () => normalize( {} ) );
	const [ name, setName ] = useState( '' );
	const [ saving, setSaving ] = useState( false );
	const [ savedFlash, setSavedFlash ] = useState( false );
	const [ viewport, setViewport ] = useState( 'desktop' );
	const [ baseline, setBaseline ] = useState( '' );
	const [ selectedElementKey, setSelectedElementKey ] = useState( null );
	const [ focusButtonIndex, setFocusButtonIndex ] = useState( null );
	const [ focusSlideIndex, setFocusSlideIndex ] = useState( null );
	const [ focusCardIndex, setFocusCardIndex ] = useState( null );
	const [ inspectorTab, setInspectorTab ] = useState( 'fields' );
	const [ tabPanelKey, setTabPanelKey ] = useState( 0 );
	const [ updateTarget, setUpdateTarget ] = useState( null );
	const selectedElementKeyRef = useRef( null );
	const inspectorTabRef = useRef( 'fields' );

	useEffect( () => {
		selectedElementKeyRef.current = selectedElementKey;
	}, [ selectedElementKey ] );

	useEffect( () => {
		inspectorTabRef.current = inspectorTab;
	}, [ inspectorTab ] );

	const isDirty = useMemo( () => {
		if ( ! baseline || ! item ) {
			return false;
		}
		return snapshotContent( { name, attrs, cssVars } ) !== baseline;
	}, [ baseline, item, name, attrs, cssVars ] );

	useUnsavedChanges( isDirty );

	useEffect( () => {
		if ( ! id ) {
			return;
		}
		api.get( `/user-contents/${ id }` ).then( ( data ) => {
			setItem( data );
			const nextAttrs = data.contentAttrs || {};
			const nextVars = normalize( data.cssVars || {} );
			const nextName = data.name || '';
			setAttrs( nextAttrs );
			setCssVars( nextVars );
			setName( nextName );
			setSelectedElementKey( null );
			setFocusButtonIndex( null );
			setFocusSlideIndex( null );
			setFocusCardIndex( null );
			setInspectorTab( 'fields' );
			setTabPanelKey( ( k ) => k + 1 );
			setBaseline(
				snapshotContent( {
					name: nextName,
					attrs: nextAttrs,
					cssVars: nextVars,
				} )
			);
		} );
	}, [ id ] );

	const preview = useMemo( () => {
		if ( ! item?.markup ) {
			return { __html: '', styleCss: '' };
		}
		if ( ! attrs || Object.keys( attrs ).length === 0 ) {
			return { __html: '', styleCss: '' };
		}
		return renderPreview(
			item.markup,
			attrs,
			cssVars,
			item.fieldSchema || [],
			viewport
		);
	}, [ item, attrs, cssVars, viewport, buttonLibrary ] );

	const enabledFields = useMemo(
		() =>
			styleableFieldsInOrder(
				item?.fieldSchema || [],
				attrs?.elementsOrder,
				{
					defaultAttrs: item?.defaultAttrs || {},
					markup: item?.markup || '',
					breakpoint: viewport,
				}
			),
		[
			item?.fieldSchema,
			item?.defaultAttrs,
			item?.markup,
			attrs?.elementsOrder,
			viewport,
		]
	);

	const selectableKeys = useMemo(
		() => enabledFields.map( ( f ) => f.key ),
		[ enabledFields ]
	);

	const selectedField = useMemo(
		() => enabledFields.find( ( f ) => f.key === selectedElementKey ) || null,
		[ enabledFields, selectedElementKey ]
	);

	useEffect( () => {
		if ( selectedElementKey && ! selectedField ) {
			setSelectedElementKey( null );
		}
	}, [ selectedElementKey, selectedField ] );

	const goToTab = useCallback( ( name ) => {
		setInspectorTab( name );
		setTabPanelKey( ( k ) => k + 1 );
	}, [] );

	const clearElementSelection = useCallback( () => {
		setSelectedElementKey( null );
		setFocusButtonIndex( null );
		setFocusSlideIndex( null );
		setFocusCardIndex( null );
	}, [] );

	const onSelectElement = useCallback(
		( key, context = null ) => {
			const next = key || null;
			const prev = selectedElementKeyRef.current;
			if (
				next &&
				next === prev &&
				context?.slideIndex == null &&
				context?.cardIndex == null &&
				context?.buttonIndex == null
			) {
				return;
			}
			setSelectedElementKey( next );
			if ( ! next ) {
				setFocusButtonIndex( null );
				setFocusSlideIndex( null );
				setFocusCardIndex( null );
			} else {
				if (
					context?.slideIndex != null &&
					Number.isFinite( Number( context.slideIndex ) )
				) {
					setFocusSlideIndex( Number( context.slideIndex ) );
					setFocusCardIndex( null );
				}
				if (
					context?.cardIndex != null &&
					Number.isFinite( Number( context.cardIndex ) )
				) {
					setFocusCardIndex( Number( context.cardIndex ) );
					setFocusSlideIndex( null );
				}
				setFocusButtonIndex(
					next === 'buttons' || context?.buttonIndex != null
						? context?.buttonIndex ?? 0
						: null
				);
				goToTab( 'styles' );
				scrollInspectorToTop();
			}
		},
		[ goToTab ]
	);

	const onElementSelect = useCallback(
		( key, context = null ) => {
			const prev = selectedElementKeyRef.current;
			const tab = inspectorTabRef.current;

			if ( key ) {
				if (
					key === prev &&
					tab === 'styles' &&
					context?.slideIndex == null &&
					context?.cardIndex == null &&
					context?.buttonIndex == null
				) {
					return;
				}
				setSelectedElementKey( key );
				if (
					context?.slideIndex != null &&
					Number.isFinite( Number( context.slideIndex ) )
				) {
					setFocusSlideIndex( Number( context.slideIndex ) );
					setFocusCardIndex( null );
				}
				if (
					context?.cardIndex != null &&
					Number.isFinite( Number( context.cardIndex ) )
				) {
					setFocusCardIndex( Number( context.cardIndex ) );
					setFocusSlideIndex( null );
				}
				setFocusButtonIndex(
					key === 'buttons' || context?.buttonIndex != null
						? context?.buttonIndex ?? 0
						: null
				);
				goToTab( 'styles' );
				scrollInspectorToTop();
				return;
			}

			setSelectedElementKey( null );
			setFocusButtonIndex( null );
			setFocusSlideIndex( null );
			setFocusCardIndex( null );
			if ( ! prev && tab === 'styles' ) {
				return;
			}
			goToTab( 'fields' );
			scrollInspectorToTop();
		},
		[ goToTab ]
	);

	const onEditButtonStyles = useCallback(
		( fieldKey, index ) => {
			setSelectedElementKey( fieldKey || 'buttons' );
			setFocusButtonIndex( typeof index === 'number' ? index : null );
			goToTab( 'styles' );
			scrollInspectorToTop();
		},
		[ goToTab ]
	);

	const onTabSelect = useCallback( ( name ) => {
		setInspectorTab( name );
		if ( name === 'order' ) {
			setSelectedElementKey( null );
			setFocusButtonIndex( null );
			setFocusSlideIndex( null );
			setFocusCardIndex( null );
		}
	}, [] );

	const inspectorAttributes = useMemo(
		() => ( { ...attrs, cssVars } ),
		[ attrs, cssVars ]
	);

	const setInspectorAttributes = useCallback( ( patch ) => {
		const next = patch || {};
		if ( Object.prototype.hasOwnProperty.call( next, 'cssVars' ) ) {
			setCssVars( normalize( next.cssVars || {} ) );
		}
		Object.entries( next ).forEach( ( [ key, value ] ) => {
			if ( key === 'cssVars' || key === 'librarySaveRev' ) {
				return;
			}
			setAttrs( setAttrIfChanged( key, value ) );
		} );
	}, [] );

	const meta = useMemo(
		() =>
			item
				? {
						fieldSchema: item.fieldSchema || [],
						styleSchema: item.styleSchema || [],
						markup: item.markup || '',
						cssVars: {},
						defaultAttrs: item.defaultAttrs || {},
				  }
				: null,
		[ item ]
	);

	const save = async () => {
		if ( saving ) {
			return;
		}
		setSaving( true );
		try {
			const updated = await api.put( `/user-contents/${ id }`, {
				name,
				contentAttrs: attrs,
				cssVars,
			} );
			setItem( updated );
			const nextVars = normalize( updated.cssVars || {} );
			const nextAttrs = updated.contentAttrs || attrs;
			const nextName = updated.name || name;
			setCssVars( nextVars );
			setAttrs( nextAttrs );
			setName( nextName );
			setBaseline(
				snapshotContent( {
					name: nextName,
					attrs: nextAttrs,
					cssVars: nextVars,
				} )
			);
			setSavedFlash( true );
			window.setTimeout( () => setSavedFlash( false ), 2200 );
		} catch ( e ) {
			window.alert( e?.message || __( 'Save failed.', 'joc-blockora-morph' ) );
		}
		setSaving( false );
	};

	useSaveShortcut( save );

	const copyShortcode = async () => {
		if ( ! item?.shortcode ) {
			return;
		}
		try {
			await navigator.clipboard.writeText( item.shortcode );
		} catch ( e ) {
			window.prompt( __( 'Copy shortcode', 'joc-blockora-morph' ), item.shortcode );
		}
	};

	if ( ! item || ! meta ) {
		return (
			<BrandLoader
				label={ __( 'Loading shortcode block…', 'joc-blockora-morph' ) }
			/>
		);
	}

	return (
		<div className="bm-content-editor flex min-h-screen flex-col bg-surface-bg font-sans text-on-background antialiased">
			<header className="bm-editor-topbar bm-glass sticky top-0 z-20 flex shrink-0 items-center gap-3 px-4 py-2.5 sm:px-5">
				<div className="bm-editor-topbar__start">
					<button
						type="button"
						className="bm-editor-topbar__brand"
						onClick={ onBack }
						aria-label={ __( 'Shortcode Blocks', 'joc-blockora-morph' ) }
					>
						<img
							className="bm-editor-topbar__logo"
							src={ brandLogoUrl() }
							alt=""
							width={ 44 }
							height={ 44 }
							decoding="async"
						/>
					</button>
				</div>

				<div className="bm-editor-topbar__title min-w-0 flex-1">
					<p className="m-0 text-caps-micro font-bold uppercase tracking-widest text-outline">
						{ __( 'Shortcode block', 'joc-blockora-morph' ) }
					</p>
					<h1 className="m-0 truncate text-headline-md text-on-surface">
						{ name || item.name }
					</h1>
					<button
						type="button"
						className="bm-editor-topbar__back"
						onClick={ onBack }
					>
						<span
							className="dashicons dashicons-arrow-left-alt2"
							aria-hidden="true"
						/>
						{ __( 'Shortcode Blocks', 'joc-blockora-morph' ) }
					</button>
				</div>

				<button
					type="button"
					className="bm-btn bm-btn--ghost bm-btn--sm"
					onClick={ copyShortcode }
				>
					{ __( 'Copy shortcode', 'joc-blockora-morph' ) }
				</button>

				<button
					type="button"
					className={
						savedFlash
							? 'bm-btn bm-btn--primary is-saved'
							: isDirty
							? 'bm-btn bm-btn--primary is-dirty'
							: 'bm-btn bm-btn--primary'
					}
					onClick={ save }
					disabled={ saving }
				>
					<span
						className={
							savedFlash
								? 'dashicons dashicons-yes-alt text-[16px]'
								: 'dashicons dashicons-cloud-saved text-[16px]'
						}
						aria-hidden="true"
					/>
					{ saving
						? __( 'Saving…', 'joc-blockora-morph' )
						: savedFlash
						? __( 'Saved', 'joc-blockora-morph' )
						: __( 'Save', 'joc-blockora-morph' ) }
				</button>
			</header>

			<div className="mx-auto w-full max-w-[1620px] flex-1 px-gutter py-stack-lg">
				<p className="mb-stack-md mt-0 max-w-xl text-body-lg text-on-surface-variant">
					{ __(
						'Fill in the real content here, then copy the shortcode and paste it in a page, widget, or template.',
						'joc-blockora-morph'
					) }
				</p>

				{ item.updateAvailable ? (
					<div className="mb-stack-md">
						<Notice variant="warning">
							<div className="flex flex-wrap items-center justify-between gap-3">
								<p className="m-0 text-body-md">
									{ __(
										'A library template update is available for the parent Gutenberg block. Review it to pick up new markup and fields (like logos).',
										'joc-blockora-morph'
									) }
								</p>
								<button
									type="button"
									className="bm-btn bm-btn--primary bm-btn--sm shrink-0"
									onClick={ () =>
										setUpdateTarget( {
											id: item.userBlockId,
											name:
												item.userBlockName || item.name,
										} )
									}
								>
									{ __( 'Review update', 'joc-blockora-morph' ) }
								</button>
							</div>
						</Notice>
					</div>
				) : null }

				<div
					className="grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_var(--bm-content-sidebar-width)]"
					style={ {
						'--bm-content-sidebar-width': `${ CONTENT_SIDEBAR_WIDTH }px`,
					} }
				>
					<div className="bm-panel-card overflow-hidden p-0">
						<div className="border-b border-outline-variant/20 px-5 pt-4">
							<code className="mb-3 inline-block rounded-lg border border-outline-variant/30 bg-surface-low px-3 py-1.5 font-mono text-[12px] text-on-surface">
								{ item.shortcode }
							</code>
						</div>
						<PreviewFrame
							html={ preview }
							className="min-h-[520px]"
							viewport={ viewport }
							onViewportChange={ setViewport }
							minHeight={ 480 }
							selectableKeys={ selectableKeys }
							selectedElementKey={ selectedElementKey }
							focusSlideIndex={ focusSlideIndex }
							focusCardIndex={ focusCardIndex }
							focusButtonIndex={ focusButtonIndex }
							onElementSelect={ onElementSelect }
							contentEditEnabled
							fieldSchema={ item?.fieldSchema || [] }
							attributes={ attrs }
							setAttributes={ setInspectorAttributes }
						/>
					</div>
					<aside className="bm-panel-card bm-content-editor__inspector space-y-4">
						<label className="block">
							<span className="bm-field-label">
								{ __( 'Name', 'joc-blockora-morph' ) }
							</span>
							<input
								type="text"
								className="bm-field-input"
								value={ name }
								onChange={ ( e ) => setName( e.target.value ) }
							/>
						</label>

						<div className="bm-gutenberg-inspector-tabs">
							<BlockInspectorPanel
								meta={ meta }
								attributes={ inspectorAttributes }
								setAttributes={ setInspectorAttributes }
								selectedElementKey={ selectedElementKey }
								onSelectElement={ onSelectElement }
								onClearElement={ clearElementSelection }
								initialTabName={ inspectorTab }
								tabPanelKey={ tabPanelKey }
								onTabSelect={ onTabSelect }
								onEditButtonStyles={ onEditButtonStyles }
								focusButtonIndex={ focusButtonIndex }
								styleBreakpoint={ viewport }
								onStyleBreakpointChange={ setViewport }
								buttonLibrary={ buttonLibrary }
								breakpointHints="frontend"
								renderRichText={ ( { field: f, value, onChange } ) => (
									<WysiwygField
										label={ f.label }
										value={ value || '' }
										onChange={ onChange }
									/>
								) }
							/>
						</div>
					</aside>
				</div>
			</div>
			<FloatingToast
				visible={ savedFlash }
				message={ __( 'Changes saved', 'joc-blockora-morph' ) }
			/>
			{ updateTarget ? (
				<TemplateUpdateModal
					blockId={ updateTarget.id }
					blockName={ updateTarget.name }
					onClose={ () => setUpdateTarget( null ) }
					onApplied={ () => {
						setUpdateTarget( null );
						api.get( `/user-contents/${ id }` ).then( ( data ) => {
							setItem( data );
							const nextAttrs = data.contentAttrs || {};
							const nextVars = normalize( data.cssVars || {} );
							const nextName = data.name || '';
							setAttrs( nextAttrs );
							setCssVars( nextVars );
							setName( nextName );
							setBaseline(
								snapshotContent( {
									name: nextName,
									attrs: nextAttrs,
									cssVars: nextVars,
								} )
							);
						} );
					} }
				/>
			) : null }
		</div>
	);
}
