<?php
/**
 * GSC Cannibalization Page.
 *
 * Tools → Cannibalization: scans the page×query cache and surfaces every
 * query where two or more of your own URLs compete — the audit's #1 root
 * cause. Read-only report; the fix (keep the strongest, 301 the rest) is
 * done in the Content Auditor's Trash + redirect tool.
 *
 * @package Platform\Helm\Modules\GSC
 */

declare( strict_types = 1 );

namespace Platform\Helm\Modules\GSC;

use Platform\Helm\Core\Assets;
use Platform\Helm\Core\Authorization;
use Platform\Helm\Core\LlmClient;
use Platform\Helm\Core\LlmSettings;
use Platform\Helm\Modules\Content_Auditor\Redirects;

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

final class GscCannibalizationPage {

	public const PAGE_SLUG   = 'platform-helm-cannibalization';
	public const PARENT_SLUG = 'platform-helm';
	private const MERGE_NONCE  = 'platform_helm_cannibal_merge';
	private const ADVISE_NONCE = 'platform_helm_cannibal_advise';

	public function __construct(
		private Assets $assets,
		private GscQueryStore $query_store
	) {}

	public function register_hooks(): void {
		add_action( 'admin_menu', [ $this, 'register_menu' ], 46 );
		add_action( 'admin_post_platform_helm_cannibal_merge', [ $this, 'handle_merge' ] );
		add_action( 'wp_ajax_platform_helm_cannibal_advise', [ $this, 'ajax_advise' ] );
	}

	public function ajax_advise(): void {
		check_ajax_referer( self::ADVISE_NONCE, 'nonce' );
		Authorization::require_master_admin_ajax();

		$query = isset( $_POST['query'] ) ? sanitize_text_field( wp_unslash( $_POST['query'] ) ) : '';
		if ( '' === $query ) {
			wp_send_json_error( [ 'message' => __( 'Missing query.', 'platform-helm' ) ] );
		}

		$pages = $this->query_store->pages_for_query( $query );
		if ( empty( $pages ) ) {
			wp_send_json_error( [ 'message' => __( 'No competing pages found for this query.', 'platform-helm' ) ] );
		}

		$lines = '';
		foreach ( array_slice( $pages, 0, 8 ) as $p ) {
			$t      = get_the_title( (int) $p['post_id'] );
			$lines .= '- ' . ( $t ?: (string) $p['url'] ) . ' — ' . (string) $p['url']
				. ' (position ' . number_format( (float) $p['position'], 1 ) . ', ' . number_format( (int) $p['impressions'] ) . " impr)\n";
		}

		$system = 'You are an SEO strategist for this website. The pages below all rank for the same Google query and cannibalize each other, splitting ranking signals. '
			. 'Recommend exactly ONE page to KEEP as canonical (and which to redirect into it), based on title/intent fit and current position — not position alone. '
			. "Reply in three short labelled parts:\nKEEP — the page and why.\nREDIRECT — which pages to 301 into it.\nMERGE IN — what content from the losers is worth moving into the keeper.\nBe concise and specific to the titles given.";
		$user = 'Query: "' . $query . "\"\nCompeting pages:\n" . $lines;

		$res = LlmClient::chat(
			[
				[ 'role' => 'system', 'content' => $system ],
				[ 'role' => 'user', 'content' => $user ],
			],
			[ 'max_tokens' => 500, 'temperature' => 0.4 ]
		);
		if ( is_wp_error( $res ) ) {
			wp_send_json_error( [ 'message' => $res->get_error_message() ] );
		}
		wp_send_json_success( [ 'advice' => $res ] );
	}

	public function register_menu(): void {
		if ( ! Authorization::is_master_admin() ) {
			return;
		}
		$hook = add_submenu_page(
			self::PARENT_SLUG,
			__( 'Cannibalization', 'platform-helm' ),
			__( 'Cannibalization', 'platform-helm' ),
			'manage_options',
			self::PAGE_SLUG,
			[ $this, 'render' ]
		);
		if ( $hook ) {
			add_action( 'load-' . $hook, [ $this, 'enqueue' ] );
		}
	}

	public function enqueue(): void {
		$this->assets->enqueue_seo_reports();
	}

	/**
	 * Redirect the ticked "loser" pages into the chosen keeper, then Trash them.
	 * Reuses the Content Auditor's Yoast 301 helper. Both actions are reversible.
	 */
	public function handle_merge(): void {
		if ( ! Authorization::is_master_admin() ) {
			wp_die( esc_html__( 'Not allowed.', 'platform-helm' ), '', [ 'response' => 403 ] );
		}
		check_admin_referer( self::MERGE_NONCE );

		$keeper = isset( $_POST['keeper_url'] ) ? esc_url_raw( wp_unslash( $_POST['keeper_url'] ) ) : '';
		$losers = ( isset( $_POST['losers'] ) && is_array( $_POST['losers'] ) )
			? array_map( 'intval', wp_unslash( $_POST['losers'] ) ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
			: [];

		$done = 0;
		if ( '' !== $keeper && ! empty( $losers ) && Redirects::available() ) {
			foreach ( $losers as $pid ) {
				if ( $pid <= 0 ) {
					continue;
				}
				$perma = get_permalink( $pid );
				if ( ! $perma || untrailingslashit( $perma ) === untrailingslashit( $keeper ) ) {
					continue; // never redirect the keeper into itself
				}
				$origin = (string) wp_make_link_relative( $perma );
				if ( '' === $origin ) {
					continue;
				}
				if ( Redirects::create_301( $origin, $keeper ) ) {
					wp_trash_post( $pid );
					$done++;
				}
			}
		}

		wp_safe_redirect( add_query_arg( [ 'page' => self::PAGE_SLUG, 'merged' => $done ], admin_url( 'admin.php' ) ) );
		exit;
	}

	public function render(): void {
		Authorization::require_master_admin();

		$has_data = $this->query_store->count_rows() > 0;
		$groups   = $has_data ? $this->query_store->cannibalized_queries( 2, 50, 100 ) : [];
		$auditor  = admin_url( 'admin.php?page=platform-helm-content-auditor' );
		?>
		<div class="wrap ph-gsc">
			<h1><?php esc_html_e( 'Keyword Cannibalization', 'platform-helm' ); ?></h1>
			<p class="ph-gsc-lead">
				<?php esc_html_e( 'Queries where two or more of your own pages compete with each other — splitting link equity and confusing Google about which page to rank. Consolidating these is the single highest-impact SEO fix on the site.', 'platform-helm' ); ?>
			</p>

			<?php if ( isset( $_GET['merged'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
				<?php $m = max( 0, (int) $_GET['merged'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
				<div class="notice notice-success inline"><p>
					<?php
					echo esc_html( sprintf(
						/* translators: %d: number of pages */
						_n( '%d page redirected into its keeper and moved to Trash.', '%d pages redirected into their keeper and moved to Trash.', $m, 'platform-helm' ),
						$m
					) );
					echo ' ';
					esc_html_e( 'Google will take days to weeks to re-crawl and drop them, so they may still appear here for a while.', 'platform-helm' );
					?>
				</p></div>
			<?php endif; ?>

			<?php
			$last      = GscSync::last_sync();
			$when      = ( $last && ! empty( $last['time'] ) )
				? sprintf( /* translators: %s human-readable time difference */ __( '%s ago', 'platform-helm' ), human_time_diff( (int) $last['time'], time() ) )
				: __( 'never', 'platform-helm' );
			$gsc_nonce = wp_create_nonce( 'platform_helm_gsc' );
			?>
			<div class="ph-gsc-card">
				<div class="ph-gsc-row" style="justify-content:space-between; align-items:center; flex-wrap:wrap; gap:10px;">
					<span><strong><?php esc_html_e( 'Data last updated:', 'platform-helm' ); ?></strong> <?php echo esc_html( $when ); ?></span>
					<span>
						<button type="button" class="button button-primary" id="ph-cannibal-refresh"><?php esc_html_e( 'Refresh from Search Console', 'platform-helm' ); ?></button>
						<span class="ph-gsc-msg" id="ph-cannibal-refresh-msg" aria-live="polite"></span>
					</span>
				</div>
				<p class="ph-gsc-hint">
					<?php esc_html_e( 'This report reads your synced Search Console data, not live Google. Two things to know: Refresh pulls the latest from Google on demand; and Google’s own data lags ~2–3 days, so after you consolidate pages it can take days to weeks for Google to re-crawl and drop the loser. A query you just fixed will keep appearing here until Google catches up — that is normal, not a failed fix.', 'platform-helm' ); ?>
				</p>
			</div>
			<script>
			( function () {
				var btn = document.getElementById( 'ph-cannibal-refresh' );
				var msg = document.getElementById( 'ph-cannibal-refresh-msg' );
				if ( ! btn ) { return; }
				btn.addEventListener( 'click', function () {
					btn.disabled = true;
					if ( msg ) { msg.textContent = <?php echo wp_json_encode( esc_html__( 'Refreshing — this can take a moment…', 'platform-helm' ) ); ?>; }
					var body = new URLSearchParams();
					body.append( 'action', 'platform_helm_gsc_sync' );
					body.append( 'nonce', <?php echo wp_json_encode( $gsc_nonce ); ?> );
					fetch( <?php echo wp_json_encode( admin_url( 'admin-ajax.php' ) ); ?>, { method: 'POST', credentials: 'same-origin', body: body } )
						.then( function ( r ) { return r.json(); } )
						.then( function () { window.location.reload(); } )
						.catch( function () {
							btn.disabled = false;
							if ( msg ) { msg.textContent = <?php echo wp_json_encode( esc_html__( 'Refresh failed — try Sync on the Search Console page.', 'platform-helm' ) ); ?>; }
						} );
				} );
			}() );
			</script>

			<?php if ( ! $has_data ) : ?>
				<div class="notice notice-warning inline"><p>
					<?php
					printf(
						/* translators: %s: settings page URL */
						wp_kses_post( __( 'No query data yet. Go to <a href="%s">Search Console</a> and run <strong>Sync now</strong> — the query layer is pulled alongside the page data.', 'platform-helm' ) ),
						esc_url( admin_url( 'admin.php?page=platform-helm-gsc' ) )
					);
					?>
				</p></div>
			<?php elseif ( empty( $groups ) ) : ?>
				<div class="notice notice-success inline"><p>
					<?php esc_html_e( 'No cannibalization detected above the impression threshold. That is a good sign — your pages are not competing with each other for meaningful queries.', 'platform-helm' ); ?>
				</p></div>
			<?php else : ?>
				<p class="ph-gsc-hint">
					<?php
					echo esc_html( sprintf(
						/* translators: %d: number of queries */
						_n( '%d query has multiple competing pages, worst first.', '%d queries have multiple competing pages, worst first.', count( $groups ), 'platform-helm' ),
						count( $groups )
					) );
					?>
					&nbsp;
					<?php
					printf(
						/* translators: %s: auditor URL */
						wp_kses_post( __( 'To fix one: open the <a href="%s">Content Auditor</a>, keep the strongest URL, and use <strong>Trash + 301 redirect</strong> on the weaker duplicates pointing at the one you keep.', 'platform-helm' ) ),
						esc_url( $auditor )
					);
					?>
				</p>

				<?php $can_redirect = Redirects::available(); ?>
				<?php $ai_on = LlmSettings::is_configured(); ?>
				<?php $advise_nonce = wp_create_nonce( self::ADVISE_NONCE ); ?>
				<?php foreach ( $groups as $g ) : ?>
					<?php $pages = $this->query_store->pages_for_query( $g['query'] ); ?>
					<details class="ph-gsc-card" style="margin-bottom:10px;">
						<summary style="cursor:pointer; font-weight:600;">
							<?php echo esc_html( $g['query'] ); ?>
							<span style="font-weight:400; color:#646970;">
								&nbsp;—&nbsp;
								<?php
								echo esc_html( sprintf(
									/* translators: 1: page count, 2: impressions, 3: clicks */
									__( '%1$d pages competing · %2$s impressions · %3$s clicks', 'platform-helm' ),
									(int) $g['pages'],
									number_format_i18n( (int) $g['impressions'] ),
									number_format_i18n( (int) $g['clicks'] )
								) );
								?>
							</span>
						</summary>
						<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
							<?php wp_nonce_field( self::MERGE_NONCE ); ?>
							<input type="hidden" name="action" value="platform_helm_cannibal_merge" />
							<table class="widefat striped ph-gsc-table" style="margin-top:10px;">
								<thead>
									<tr>
										<th style="width:48px;"><?php esc_html_e( 'Keep', 'platform-helm' ); ?></th>
										<th style="width:64px;"><?php esc_html_e( 'Redirect', 'platform-helm' ); ?></th>
										<th><?php esc_html_e( 'Competing page', 'platform-helm' ); ?></th>
										<th><?php esc_html_e( 'Position', 'platform-helm' ); ?></th>
										<th><?php esc_html_e( 'Clicks', 'platform-helm' ); ?></th>
										<th><?php esc_html_e( 'Impressions', 'platform-helm' ); ?></th>
										<th></th>
									</tr>
								</thead>
								<tbody>
									<?php foreach ( $pages as $i => $p ) : ?>
										<?php $pid = (int) $p['post_id']; ?>
										<tr>
											<td><input type="radio" name="keeper_url" value="<?php echo esc_attr( (string) $p['url'] ); ?>" <?php checked( 0 === (int) $i ); ?> /></td>
											<td><?php if ( $pid > 0 ) : ?><input type="checkbox" class="ph-loser" name="losers[]" value="<?php echo esc_attr( (string) $pid ); ?>" <?php checked( 0 !== (int) $i ); ?> /><?php endif; ?></td>
											<td>
												<?php $title = get_the_title( $pid ); ?>
												<a href="<?php echo esc_url( (string) get_edit_post_link( $pid ) ); ?>"><?php echo esc_html( $title ?: (string) $p['url'] ); ?></a>
												<?php if ( 0 === (int) $i ) : ?>
													<span class="ph-gsc-pill" style="background:#d7f0d7; color:#1a4d1a; padding:1px 7px; border-radius:10px; font-size:11px; margin-left:6px;"><?php esc_html_e( 'best position — likely keep', 'platform-helm' ); ?></span>
												<?php endif; ?>
												<div style="font-size:11px; color:#646970;"><?php echo esc_html( (string) $p['url'] ); ?></div>
											</td>
											<td><?php echo esc_html( number_format( (float) $p['position'], 1 ) ); ?></td>
											<td><?php echo esc_html( number_format_i18n( (int) $p['clicks'] ) ); ?></td>
											<td><?php echo esc_html( number_format_i18n( (int) $p['impressions'] ) ); ?></td>
											<td><a class="button button-small" target="_blank" rel="noopener" href="<?php echo esc_url( (string) get_permalink( $pid ) ); ?>"><?php esc_html_e( 'View', 'platform-helm' ); ?></a></td>
										</tr>
									<?php endforeach; ?>
								</tbody>
							</table>
							<?php if ( $can_redirect ) : ?>
								<p style="margin:10px 0 4px;">
									<button type="submit" class="button button-primary"><?php esc_html_e( 'Redirect & retire the ticked pages into the kept one', 'platform-helm' ); ?></button>
									<span class="ph-gsc-hint"><?php esc_html_e( 'Creates a 301 from each ticked page to the kept URL and moves it to Trash. Both are reversible.', 'platform-helm' ); ?></span>
								</p>
							<?php else : ?>
								<p class="ph-gsc-hint"><?php esc_html_e( 'Auto-redirect needs Yoast SEO Premium (redirects). Keep the strongest URL and 301 the rest manually.', 'platform-helm' ); ?></p>
							<?php endif; ?>
							<?php if ( $ai_on ) : ?>
								<p style="margin:8px 0 4px;">
									<button type="button" class="button ph-cannibal-advise" data-query="<?php echo esc_attr( (string) $g['query'] ); ?>"><?php esc_html_e( 'AI advice: which to keep?', 'platform-helm' ); ?></button>
								</p>
								<div class="ph-cannibal-advice" style="display:none; white-space:pre-wrap; line-height:1.55; background:#f3fbf5; border:1px solid #d8ecdd; border-radius:6px; padding:12px; margin-bottom:6px;"></div>
							<?php endif; ?>
						</form>
					</details>
				<?php endforeach; ?>

				<?php if ( $ai_on ) : ?>
				<script>
				( function () {
					var url = <?php echo wp_json_encode( admin_url( 'admin-ajax.php' ) ); ?>;
					var nonce = <?php echo wp_json_encode( $advise_nonce ); ?>;
					document.addEventListener( 'click', function ( e ) {
						var btn = ( e.target && e.target.closest ) ? e.target.closest( '.ph-cannibal-advise' ) : null;
						if ( ! btn ) { return; }
						e.preventDefault();
						var box = btn.closest( 'details' ).querySelector( '.ph-cannibal-advice' );
						if ( ! box ) { return; }
						box.style.display = ''; box.textContent = 'Analysing the competing pages…';
						btn.disabled = true; var lbl = btn.textContent; btn.textContent = 'Thinking…';
						var body = new URLSearchParams();
						body.append( 'action', 'platform_helm_cannibal_advise' );
						body.append( 'nonce', nonce );
						body.append( 'query', btn.getAttribute( 'data-query' ) );
						fetch( url, { method: 'POST', credentials: 'same-origin', body: body } )
							.then( function ( r ) { return r.json(); } )
							.then( function ( res ) {
								btn.disabled = false; btn.textContent = lbl;
								box.textContent = ( res && res.success && res.data ) ? res.data.advice : ( ( res && res.data && res.data.message ) ? res.data.message : 'Could not generate advice.' );
							} )
							.catch( function () { btn.disabled = false; btn.textContent = lbl; box.textContent = 'Request failed — try again.'; } );
					} );
				}() );
				</script>
				<?php endif; ?>
			<?php endif; ?>
		</div>
		<?php
	}
}
