<?php
/**
 * Suggester Meta Box.
 *
 * Adds an "Internal Link Suggestions" panel to the post editor. The panel has
 * a "Scan content" button that posts the current editor content to an AJAX
 * endpoint and lists suggested internal (and rule-based external) links.
 *
 * For each suggestion the writer gets the matched phrase + context snippet, the
 * target, "Insert link" (classic editor: drops the anchor in place at the first
 * clean occurrence), and "Copy URL".
 *
 * Note on Gutenberg one-click insert: reliably mutating the block editor's
 * React-managed state from a PHP meta box isn't dependable, so in the block
 * editor we fall back to copy-to-clipboard. A dedicated block-editor plugin for
 * true one-click Gutenberg insertion is a future enhancement.
 *
 * @package Platform\Helm\Modules\Link_Suggester
 */

declare( strict_types = 1 );

namespace Platform\Helm\Modules\Link_Suggester;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

final class SuggesterMetaBox {

	public const AJAX_ACTION = 'platform_helm_link_suggest';
	public const NONCE       = 'platform_helm_link_suggest_nonce';

	private SuggestionEngine $engine;

	public function __construct() {
		$this->engine = new SuggestionEngine();
	}

	public function register_hooks(): void {
		add_action( 'add_meta_boxes', [ $this, 'register_meta_box' ] );
		add_action( 'wp_ajax_' . self::AJAX_ACTION, [ $this, 'ajax_suggest' ] );
	}

	public function register_meta_box(): void {
		$settings = SuggesterSettings::get();
		if ( ! $settings['enabled'] ) {
			return;
		}
		foreach ( $settings['editor_post_types'] as $type ) {
			add_meta_box(
				'platform_helm_link_suggestions',
				__( '🔗 Internal Link Suggestions', 'platform-helm' ),
				[ $this, 'render_meta_box' ],
				$type,
				'side',
				'high',
				[
					// In the block editor this classic meta box is replaced by
					// the Gutenberg sidebar panel (GutenbergPanel). Marking it
					// __back_compat_meta_box tells WordPress to hide it under
					// Gutenberg automatically, so we never show both. It still
					// renders normally in the classic editor.
					'__back_compat_meta_box' => true,
				]
			);
		}
	}

	public function render_meta_box( \WP_Post $post ): void {
		$nonce = wp_create_nonce( self::NONCE );
		?>
		<div class="phelm-ls" data-post="<?php echo (int) $post->ID; ?>" data-nonce="<?php echo esc_attr( $nonce ); ?>">
			<p style="margin-top:0;color:#555;font-size:12px;">
				<?php esc_html_e( 'Find existing posts you could link to from this content.', 'platform-helm' ); ?>
			</p>
			<button type="button" class="button button-secondary phelm-ls-scan" style="width:100%;">
				<?php esc_html_e( 'Scan content for links', 'platform-helm' ); ?>
			</button>
			<div class="phelm-ls-status" style="font-size:12px;color:#777;margin-top:8px;display:none;"></div>
			<div class="phelm-ls-results" style="margin-top:10px;"></div>
		</div>

		<style>
		.phelm-ls-item{border:1px solid #e0e0e0;border-radius:6px;padding:8px 10px;margin-bottom:8px;background:#fff;}
		.phelm-ls-item .ls-phrase{font-weight:600;color:#0047AB;font-size:12px;}
		.phelm-ls-item .ls-ext{display:inline-block;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.3px;color:#7A5C00;background:#FFF3CD;border:1px solid #FFE08A;border-radius:3px;padding:0 4px;vertical-align:middle;}
		.phelm-ls-item .ls-target{font-size:11px;color:#555;margin:2px 0 4px;}
		.phelm-ls-item .ls-snippet{font-size:11px;color:#888;font-style:italic;margin-bottom:6px;line-height:1.4;}
		.phelm-ls-item .ls-actions{display:flex;gap:6px;}
		.phelm-ls-item .ls-actions button{font-size:11px;cursor:pointer;border:1px solid #c3c4c7;background:#f6f7f7;border-radius:4px;padding:2px 8px;}
		.phelm-ls-item .ls-actions button:hover{background:#eef;}
		.phelm-ls-empty{font-size:12px;color:#777;}
		</style>

		<script>
		( function () {
			var box = document.querySelector( '.phelm-ls[data-post="<?php echo (int) $post->ID; ?>"]' );
			if ( ! box ) { return; }
			var scanBtn = box.querySelector( '.phelm-ls-scan' );
			var status  = box.querySelector( '.phelm-ls-status' );
			var results = box.querySelector( '.phelm-ls-results' );
			var ajaxUrl = <?php echo wp_json_encode( admin_url( 'admin-ajax.php' ) ); ?>;
			var nonce   = box.getAttribute( 'data-nonce' );
			var postId  = box.getAttribute( 'data-post' );

			function getEditorContent() {
				try {
					if ( window.wp && wp.data && wp.data.select( 'core/editor' ) ) {
						var c = wp.data.select( 'core/editor' ).getEditedPostContent();
						if ( c ) { return c; }
					}
				} catch ( e ) {}
				try {
					if ( window.tinymce && tinymce.activeEditor && ! tinymce.activeEditor.isHidden() ) {
						return tinymce.activeEditor.getContent();
					}
				} catch ( e ) {}
				var ta = document.getElementById( 'content' );
				return ta ? ta.value : '';
			}

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

			function insertClassicInPlace( phrase, url, isExternal ) {
				var ed = null;
				try {
					if ( window.tinymce && tinymce.activeEditor && ! tinymce.activeEditor.isHidden() ) {
						ed = tinymce.activeEditor;
					}
				} catch ( e ) {}
				if ( ! ed ) { return false; }

				var doc  = ed.getDoc();
				var body = ed.getBody();
				if ( ! doc || ! body ) { return false; }

				var walker = doc.createTreeWalker( body, 4, null, false );
				var node;
				var needle = phrase.toLowerCase();
				while ( ( node = walker.nextNode() ) ) {
					var p = node.parentNode, inAnchor = false;
					while ( p && p !== body ) {
						if ( p.nodeName && p.nodeName.toLowerCase() === 'a' ) { inAnchor = true; break; }
						p = p.parentNode;
					}
					if ( inAnchor ) { continue; }

					var text = node.nodeValue || '';
					var idx  = text.toLowerCase().indexOf( needle );
					if ( idx === -1 ) { continue; }

					if ( isExternal ) {
						var ok = false;
						while ( idx !== -1 ) {
							var b = idx === 0 ? '' : text.charAt( idx - 1 );
							var a = text.charAt( idx + phrase.length );
							var isWord = function ( c ) { return /[\p{L}\p{N}]/u.test( c ); };
							if ( ( '' === b || ! isWord( b ) ) && ( '' === a || ! isWord( a ) ) ) { ok = true; break; }
							idx = text.toLowerCase().indexOf( needle, idx + 1 );
						}
						if ( ! ok ) { continue; }
					}

					var before = text.slice( 0, idx );
					var match  = text.slice( idx, idx + phrase.length );
					var after  = text.slice( idx + phrase.length );

					var frag = doc.createDocumentFragment();
					if ( before ) { frag.appendChild( doc.createTextNode( before ) ); }
					var link = doc.createElement( 'a' );
					link.setAttribute( 'href', url );
					if ( isExternal ) {
						link.setAttribute( 'target', '_blank' );
						link.setAttribute( 'rel', 'noopener noreferrer' );
					}
					link.textContent = match;
					frag.appendChild( link );
					if ( after ) { frag.appendChild( doc.createTextNode( after ) ); }

					node.parentNode.replaceChild( frag, node );

					ed.setDirty( true );
					try { ed.nodeChanged(); ed.fire( 'change' ); ed.save(); } catch ( e ) {}
					return true;
				}
				return false;
			}

			function render( items ) {
				results.innerHTML = '';
				if ( ! items.length ) {
					results.innerHTML = '<p class="phelm-ls-empty"><?php echo esc_js( __( 'No internal link opportunities found in this content.', 'platform-helm' ) ); ?></p>';
					return;
				}
				items.forEach( function ( it ) {
					var el = document.createElement( 'div' );
					el.className = 'phelm-ls-item';
					var badge = it.is_external
						? '<span class="ls-ext"><?php echo esc_js( __( 'external', 'platform-helm' ) ); ?></span>'
						: '';
					el.innerHTML =
						'<div class="ls-phrase">' + esc( it.phrase ) + ' ' + badge + '</div>' +
						'<div class="ls-target">→ ' + esc( it.target_title ) + '</div>' +
						( it.snippet ? '<div class="ls-snippet">' + esc( it.snippet ) + '</div>' : '' ) +
						'<div class="ls-actions">' +
							'<button type="button" class="ls-insert"><?php echo esc_js( __( 'Insert link', 'platform-helm' ) ); ?></button>' +
							'<button type="button" class="ls-copy"><?php echo esc_js( __( 'Copy URL', 'platform-helm' ) ); ?></button>' +
						'</div>';

					el.querySelector( '.ls-copy' ).addEventListener( 'click', function () {
						var b = this;
						navigator.clipboard.writeText( it.target_url ).then( function () {
							b.textContent = '<?php echo esc_js( __( 'URL copied!', 'platform-helm' ) ); ?>';
							setTimeout( function () { b.textContent = '<?php echo esc_js( __( 'Copy URL', 'platform-helm' ) ); ?>'; }, 1500 );
						} );
					} );

					el.querySelector( '.ls-insert' ).addEventListener( 'click', function () {
						var b  = this;
						var ok = insertClassicInPlace( it.phrase, it.target_url, !! it.is_external );
						if ( ok ) {
							b.textContent = '<?php echo esc_js( __( 'Linked ✓', 'platform-helm' ) ); ?>';
							b.disabled = true;
							el.style.opacity = '0.55';
						} else {
							navigator.clipboard.writeText( it.target_url );
							b.textContent = '<?php echo esc_js( __( 'URL copied — select the words & link', 'platform-helm' ) ); ?>';
						}
					} );

					results.appendChild( el );
				} );
			}

			scanBtn.addEventListener( 'click', function () {
				var content = getEditorContent();
				status.style.display = 'block';
				status.textContent = '<?php echo esc_js( __( 'Scanning…', 'platform-helm' ) ); ?>';
				results.innerHTML = '';

				var body = new URLSearchParams();
				body.append( 'action', '<?php echo esc_js( self::AJAX_ACTION ); ?>' );
				body.append( 'nonce', nonce );
				body.append( 'post_id', postId );
				body.append( 'content', content );

				fetch( ajaxUrl, { method: 'POST', credentials: 'same-origin', body: body } )
					.then( function ( r ) { return r.json(); } )
					.then( function ( res ) {
						status.style.display = 'none';
						if ( res && res.success ) {
							render( res.data.suggestions || [] );
						} else {
							results.innerHTML = '<p class="phelm-ls-empty"><?php echo esc_js( __( 'Scan failed. Try again.', 'platform-helm' ) ); ?></p>';
						}
					} )
					.catch( function () {
						status.style.display = 'none';
						results.innerHTML = '<p class="phelm-ls-empty"><?php echo esc_js( __( 'Scan failed (network).', 'platform-helm' ) ); ?></p>';
					} );
			} );
		} )();
		</script>
		<?php
	}

	public function ajax_suggest(): void {
		check_ajax_referer( self::NONCE, 'nonce' );

		$post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
		if ( $post_id <= 0 || ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( [ 'message' => __( 'Permission denied.', 'platform-helm' ) ], 403 );
		}

		// Content can contain HTML; we only read it, never store it. Unslash
		// but do not over-sanitise, or we lose the markup we need to parse.
		$content = isset( $_POST['content'] ) ? wp_unslash( (string) $_POST['content'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput

		$suggestions = $this->engine->suggest( $post_id, $content );

		wp_send_json_success( [ 'suggestions' => $suggestions ] );
	}
}
