/**
 * Platform Helm — Internal Link Suggester, Gutenberg sidebar panel.
 *
 * No-build: plain JavaScript using the global wp.* packages (no JSX, no
 * compile step). Mirrors the classic-editor meta box, reusing the same AJAX
 * endpoint, but inserts links into Gutenberg block content via the block
 * editor data store.
 *
 * Insertion strategy
 * ------------------
 * Blocks are React-managed state, not DOM, so we cannot walk the editor DOM
 * the way the classic (TinyMCE) path does. Instead we:
 *   1. Read all blocks from core/block-editor.
 *   2. Find the first block whose `content` attribute (paragraph, heading,
 *      list-item, quote, etc.) contains the phrase as real text — outside any
 *      existing anchor, and (for whole-word/external matches) on a word
 *      boundary.
 *   3. Rewrite that attribute, wrapping the matched phrase in an <a>, and
 *      write it back with updateBlockAttributes(). Gutenberg re-renders.
 *
 * If no suitable block is found we fall back to copying the URL, exactly like
 * the classic path's fallback.
 *
 * @package Platform\Helm
 */
( function ( wp ) {
	'use strict';

	if ( ! wp || ! wp.plugins || ! wp.editPost || ! wp.element || ! wp.data ) {
		return;
	}

	var CFG = window.PlatformHelmLSGutenberg || {};
	var I18N = CFG.i18n || {};

	var el = wp.element.createElement;
	var useState = wp.element.useState;
	var Fragment = wp.element.Fragment;
	var registerPlugin = wp.plugins.registerPlugin;
	var PluginSidebar = wp.editPost.PluginSidebar;
	var PluginSidebarMoreMenuItem = wp.editPost.PluginSidebarMoreMenuItem;
	var Button = wp.components.Button;
	var Spinner = wp.components.Spinner;

	/* ---------- helpers ---------- */

	function t( key, fallback ) {
		return I18N[ key ] || fallback || key;
	}

	function escapeHtml( s ) {
		var d = document.createElement( 'div' );
		d.textContent = s;
		return d.innerHTML;
	}

	function isWordChar( c ) {
		return c && /[\p{L}\p{N}]/u.test( c );
	}

	// Block types whose `content` attribute holds inline HTML we can edit.
	var CONTENT_BLOCKS = [
		'core/paragraph',
		'core/list-item',
		'core/quote',
		'core/verse',
		'core/preformatted'
	];

	/**
	 * Try to insert a link into the block content. Returns true on success.
	 *
	 * @param {string}  phrase
	 * @param {string}  url
	 * @param {boolean} isExternal
	 */
	function insertIntoBlocks( phrase, url, isExternal ) {
		var select = wp.data.select( 'core/block-editor' );
		var dispatch = wp.data.dispatch( 'core/block-editor' );
		if ( ! select || ! dispatch ) {
			return false;
		}

		var blocks = select.getBlocks();
		var needle = phrase.toLowerCase();

		// Flatten nested blocks (lists, groups) into a single ordered list.
		var flat = [];
		( function walk( list ) {
			list.forEach( function ( b ) {
				flat.push( b );
				if ( b.innerBlocks && b.innerBlocks.length ) {
					walk( b.innerBlocks );
				}
			} );
		} )( blocks );

		for ( var i = 0; i < flat.length; i++ ) {
			var block = flat[ i ];
			if ( CONTENT_BLOCKS.indexOf( block.name ) === -1 ) {
				continue;
			}
			var html = ( block.attributes && typeof block.attributes.content === 'string' )
				? block.attributes.content
				: '';
			if ( ! html ) {
				continue;
			}

			var result = wrapPhraseInHtml( html, phrase, needle, isExternal, url );
			if ( result !== null ) {
				dispatch.updateBlockAttributes( block.clientId, { content: result } );
				return true;
			}
		}
		return false;
	}

	/**
	 * Wrap the first contextually-valid occurrence of `phrase` in `html` with
	 * an anchor. Returns the new HTML, or null if no valid occurrence exists.
	 *
	 * "Valid" = the phrase appears as plain text (not inside an existing tag or
	 * anchor) and, for external/whole-word matches, on a word boundary.
	 */
	function wrapPhraseInHtml( html, phrase, needle, isExternal, url ) {
		// Walk the HTML and track whether we're inside a tag or an <a>.
		var lower = html.toLowerCase();
		var inTag = false;
		var anchorDepth = 0;
		var i = 0;

		while ( i < html.length ) {
			var ch = html[ i ];

			if ( ch === '<' ) {
				// Detect anchor open/close to avoid linking inside a link.
				var rest = lower.slice( i );
				if ( rest.indexOf( '<a' ) === 0 && ( rest[ 2 ] === ' ' || rest[ 2 ] === '>' ) ) {
					anchorDepth++;
				} else if ( rest.indexOf( '</a' ) === 0 ) {
					anchorDepth = Math.max( 0, anchorDepth - 1 );
				}
				inTag = true;
				i++;
				continue;
			}
			if ( ch === '>' ) {
				inTag = false;
				i++;
				continue;
			}
			if ( inTag || anchorDepth > 0 ) {
				i++;
				continue;
			}

			// We're in plain text. Test for the phrase at this position.
			if ( lower.slice( i, i + needle.length ) === needle ) {
				var before = i === 0 ? '' : html[ i - 1 ];
				var after = html[ i + phrase.length ] || '';
				var boundaryOk = ! isExternal || ( ! isWordChar( before ) && ! isWordChar( after ) );
				if ( boundaryOk ) {
					var matched = html.substr( i, phrase.length );
					var anchor = '<a href="' + escapeAttr( url ) + '"'
						+ ( isExternal ? ' target="_blank" rel="noopener noreferrer"' : '' )
						+ '>' + matched + '</a>';
					return html.slice( 0, i ) + anchor + html.slice( i + phrase.length );
				}
			}
			i++;
		}
		return null;
	}

	function escapeAttr( s ) {
		return String( s ).replace( /"/g, '&quot;' ).replace( /</g, '&lt;' ).replace( />/g, '&gt;' );
	}

	function getEditorContent() {
		try {
			var c = wp.data.select( 'core/editor' ).getEditedPostContent();
			if ( c ) {
				return c;
			}
		} catch ( e ) {}
		return '';
	}

	function getPostId() {
		try {
			return wp.data.select( 'core/editor' ).getCurrentPostId() || 0;
		} catch ( e ) {
			return 0;
		}
	}

	/* ---------- the panel component ---------- */

	function SuggesterPanel() {
		var stateScanning = useState( false );
		var scanning = stateScanning[ 0 ], setScanning = stateScanning[ 1 ];

		var stateItems = useState( null ); // null = not scanned yet
		var items = stateItems[ 0 ], setItems = stateItems[ 1 ];

		var stateErr = useState( '' );
		var err = stateErr[ 0 ], setErr = stateErr[ 1 ];

		// Track per-item UI status: { idx: 'linked' | 'copied' | 'notfound' }
		var stateStatus = useState( {} );
		var status = stateStatus[ 0 ], setStatus = stateStatus[ 1 ];

		function scan() {
			setScanning( true );
			setErr( '' );
			setStatus( {} );

			var body = new URLSearchParams();
			body.append( 'action', CFG.action );
			body.append( 'nonce', CFG.nonce );
			body.append( 'post_id', getPostId() );
			body.append( 'content', getEditorContent() );

			fetch( CFG.ajaxUrl, { method: 'POST', credentials: 'same-origin', body: body } )
				.then( function ( r ) { return r.json(); } )
				.then( function ( res ) {
					setScanning( false );
					if ( res && res.success && res.data && res.data.suggestions ) {
						setItems( res.data.suggestions );
					} else {
						setItems( [] );
						setErr( t( 'failed', 'Scan failed. Try again.' ) );
					}
				} )
				.catch( function () {
					setScanning( false );
					setItems( [] );
					setErr( t( 'failed', 'Scan failed. Try again.' ) );
				} );
		}

		function doInsert( item, idx ) {
			var ok = insertIntoBlocks( item.phrase, item.target_url, !! item.is_external );
			var next = Object.assign( {}, status );
			if ( ok ) {
				next[ idx ] = 'linked';
			} else {
				// Fallback: copy URL, like the classic path.
				try { navigator.clipboard.writeText( item.target_url ); } catch ( e ) {}
				next[ idx ] = 'notfound';
			}
			setStatus( next );
		}

		function doCopy( item, idx ) {
			try { navigator.clipboard.writeText( item.target_url ); } catch ( e ) {}
			var next = Object.assign( {}, status );
			next[ idx ] = 'copied';
			setStatus( next );
		}

		var children = [];

		children.push(
			el( 'p', { className: 'phelm-gb-intro', key: 'intro' }, t( 'intro', '' ) )
		);

		children.push(
			el( Button, {
				key: 'scanbtn',
				variant: 'secondary',
				onClick: scan,
				disabled: scanning,
				style: { width: '100%', justifyContent: 'center' }
			}, scanning ? t( 'scanning', 'Scanning…' ) : ( items === null ? t( 'scan', 'Scan content for links' ) : t( 'rescan', 'Rescan' ) ) )
		);

		if ( scanning ) {
			children.push( el( 'div', { key: 'spin', style: { textAlign: 'center', marginTop: '12px' } }, el( Spinner, {} ) ) );
		}

		if ( err ) {
			children.push( el( 'p', { className: 'phelm-gb-empty', key: 'err', style: { marginTop: '10px' } }, err ) );
		}

		if ( items !== null && ! scanning && ! err ) {
			if ( items.length === 0 ) {
				children.push( el( 'p', { className: 'phelm-gb-empty', key: 'none', style: { marginTop: '10px' } }, t( 'none', '' ) ) );
			} else {
				var list = items.map( function ( item, idx ) {
					var st = status[ idx ];
					var rowChildren = [];

					rowChildren.push(
						el( 'div', { className: 'phelm-gb-phrase', key: 'p' },
							item.phrase,
							item.is_external ? el( 'span', { className: 'phelm-gb-ext' }, t( 'external', 'external' ) ) : null
						)
					);
					rowChildren.push( el( 'div', { className: 'phelm-gb-target', key: 'tg' }, '→ ' + ( item.target_title || '' ) ) );
					if ( item.snippet ) {
						rowChildren.push( el( 'div', { className: 'phelm-gb-snippet', key: 'sn' }, item.snippet ) );
					}

					rowChildren.push(
						el( 'div', { className: 'phelm-gb-actions', key: 'ac' },
							el( Button, {
								variant: 'primary',
								isSmall: true,
								disabled: st === 'linked',
								onClick: function () { doInsert( item, idx ); }
							}, st === 'linked' ? t( 'insertedOk', 'Linked ✓' ) : t( 'insert', 'Insert link' ) ),
							el( Button, {
								variant: 'tertiary',
								isSmall: true,
								onClick: function () { doCopy( item, idx ); }
							}, st === 'copied' ? t( 'copied', 'Copied!' ) : t( 'copy', 'Copy URL' ) )
						)
					);

					if ( st === 'notfound' ) {
						rowChildren.push( el( 'div', { className: 'phelm-gb-note', key: 'nf' }, t( 'notFound', '' ) + ' — ' + t( 'copied', 'Copied!' ) ) );
					}

					return el( 'div', { className: 'phelm-gb-item', key: idx }, rowChildren );
				} );
				children.push( el( 'div', { key: 'list', style: { marginTop: '12px' } }, list ) );
			}
		}

		return el( Fragment, {},
			el( PluginSidebarMoreMenuItem, { target: 'platform-helm-link-suggester' }, t( 'panelTitle', 'Internal Link Suggestions' ) ),
			el( PluginSidebar, {
				name: 'platform-helm-link-suggester',
				title: t( 'panelTitle', 'Internal Link Suggestions' ),
				icon: 'admin-links'
			}, el( 'div', { style: { padding: '16px' } }, children ) )
		);
	}

	registerPlugin( 'platform-helm-link-suggester', {
		render: SuggesterPanel,
		icon: 'admin-links'
	} );

} )( window.wp );
