<?php
/**
 * GSC Off-Brand Flagger.
 *
 * Tools → Off-Brand Pages: surfaces high-impression, near-zero-CTR pages —
 * the "topicality bleed" from the audit (Paxi, Sage, Afrihost, Takealot).
 * For each, it shows the queries Google ranks it for and highlights any that
 * match your editable off-brand term list, so you can decide: rewrite for
 * on-topic intent, noindex, or redirect. Reads existing cached data — no extra
 * API calls. The actions live in the Content Auditor.
 *
 * @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;

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

final class GscOffBrandPage {

	public const PAGE_SLUG    = 'platform-helm-offbrand';
	public const PARENT_SLUG  = 'platform-helm';
	private const NONCE         = 'platform_helm_offbrand';
	private const VERDICT_NONCE = 'platform_helm_offbrand_verdict';
	private const TERMS_OPTION = 'platform_helm_offbrand_terms';
	private const MIN_IMPR     = 20000;
	private const MAX_CTR      = 0.005; // 0.5%

	/** Seeded from the SEO audit's off-brand offenders. */
	private const DEFAULT_TERMS = [ 'paxi', 'sage', 'afrihost', 'takealot', 'shein', 'payfast', 'pudo', 'courier guy' ];

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

	public function register_hooks(): void {
		add_action( 'admin_menu', [ $this, 'register_menu' ], 50 );
		add_action( 'wp_ajax_platform_helm_offbrand_verdict', [ $this, 'ajax_verdict' ] );
	}

	public function ajax_verdict(): void {
		check_ajax_referer( self::VERDICT_NONCE, 'nonce' );
		Authorization::require_master_admin_ajax();

		$post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
		$post    = $post_id > 0 ? get_post( $post_id ) : null;
		if ( ! $post ) {
			wp_send_json_error( [ 'message' => __( 'Post not found.', 'platform-helm' ) ] );
		}

		$title = get_the_title( $post );
		$url   = (string) get_permalink( $post );
		$top   = $this->query_store->top_queries_for_posts( [ $post_id ], 8 );
		$qlines = '';
		foreach ( (array) ( $top[ $post_id ] ?? [] ) as $q ) {
			$qlines .= '- ' . (string) $q['query'] . ' (' . number_format( (int) $q['impressions'] ) . ' impr, pos ' . number_format( (float) $q['position'], 1 ) . ")\n";
		}
		if ( '' === $qlines ) {
			$qlines = "(no query data)\n";
		}

		$system = 'You are an SEO strategist for this website. '
			. 'This page gets search impressions but very few clicks — likely an intent mismatch (Google ranks it for queries this site does not want to serve). '
			. "Give ONE clear verdict and why, choosing from:\nREFRAME — rewrite around the page's intended use case (suggest the specific angle).\nNOINDEX — keep for users but remove from search (it will never convert).\nREDIRECT — 301 into a stronger, on-brand page (say which kind).\n"
			. 'Start with the verdict word, then 2–4 sentences of specific reasoning referencing the actual queries. Be decisive and concise.';
		$user = 'Page: ' . $title . "\nURL: " . $url . "\nRanking for:\n" . $qlines;

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

	public function register_menu(): void {
		if ( ! Authorization::is_master_admin() ) {
			return;
		}
		$hook = add_submenu_page(
			self::PARENT_SLUG,
			__( 'Off-Brand Pages', 'platform-helm' ),
			__( 'Off-Brand Pages', '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();
	}

	/** @return array<int,string> */
	private function terms(): array {
		$v = get_option( self::TERMS_OPTION, null );
		if ( ! is_array( $v ) ) {
			return self::DEFAULT_TERMS;
		}
		return array_values( array_filter( array_map( 'strval', $v ) ) );
	}

	private function handle_post(): void {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing
		if ( empty( $_POST['topla_offbrand_save'] ) ) {
			return;
		}
		if ( ! Authorization::is_master_admin() ) {
			return;
		}
		check_admin_referer( self::NONCE );
		$raw   = isset( $_POST['topla_offbrand_terms'] ) ? sanitize_textarea_field( wp_unslash( $_POST['topla_offbrand_terms'] ) ) : '';
		$terms = array_filter( array_map( 'trim', preg_split( '/[\n,]+/', strtolower( $raw ) ) ?: [] ) );
		update_option( self::TERMS_OPTION, array_values( array_unique( $terms ) ), false );
	}

	private function matches_offbrand( string $query, array $terms ): bool {
		$q = strtolower( $query );
		foreach ( $terms as $t ) {
			if ( '' !== $t && false !== strpos( $q, $t ) ) {
				return true;
			}
		}
		return false;
	}

	public function render(): void {
		Authorization::require_master_admin();
		$this->handle_post();

		$terms   = $this->terms();
		$has_data = $this->store->count_rows() > 0;
		$pages    = $has_data ? $this->store->low_ctr_pages( self::MIN_IMPR, self::MAX_CTR, 100 ) : [];
		$ids      = array_map( static fn( $p ) => (int) $p['post_id'], $pages );
		$top_q    = ! empty( $ids ) ? $this->query_store->top_queries_for_posts( $ids, 4 ) : [];
		$auditor  = admin_url( 'admin.php?page=platform-helm-content-auditor' );
		$ai_on        = LlmSettings::is_configured();
		$verdict_nonce = wp_create_nonce( self::VERDICT_NONCE );
		?>
		<div class="wrap ph-gsc">
			<h1><?php esc_html_e( 'Off-Brand / Intent-Mismatch Pages', 'platform-helm' ); ?></h1>
			<p class="ph-gsc-lead">
				<?php
				echo esc_html( sprintf(
					/* translators: 1: impressions, 2: ctr percentage */
					__( 'Pages with %1$s+ impressions but under %2$s CTR — lots of search exposure, almost no clicks. Usually an intent mismatch: Google ranks the page for queries your readers do not actually want from you. Left unchecked, this drags down the whole site’s topical signal.', 'platform-helm' ),
					number_format_i18n( self::MIN_IMPR ),
					( self::MAX_CTR * 100 ) . '%'
				) );
				?>
			</p>

			<details class="ph-gsc-card">
				<summary style="cursor:pointer; font-weight:600;"><?php esc_html_e( 'Off-brand terms (used to highlight matches)', 'platform-helm' ); ?></summary>
				<form method="post" style="margin-top:10px;">
					<?php wp_nonce_field( self::NONCE ); ?>
					<input type="hidden" name="topla_offbrand_save" value="1" />
					<p class="ph-gsc-hint"><?php esc_html_e( 'One per line or comma-separated. Brand/product names that are off-topic for your site. Matching queries get flagged below.', 'platform-helm' ); ?></p>
					<textarea name="topla_offbrand_terms" class="ph-gsc-textarea" rows="4" style="width:100%; max-width:520px;"><?php echo esc_textarea( implode( "\n", $terms ) ); ?></textarea>
					<div class="ph-gsc-actions"><button type="submit" class="button button-primary"><?php esc_html_e( 'Save terms', 'platform-helm' ); ?></button></div>
				</form>
			</details>

			<?php if ( ! $has_data ) : ?>
				<div class="notice notice-warning inline"><p>
					<?php
					printf(
						/* translators: %s: settings URL */
						wp_kses_post( __( 'No traffic data yet. Run a sync on the <a href="%s">Search Console</a> screen first.', 'platform-helm' ) ),
						esc_url( admin_url( 'admin.php?page=platform-helm-gsc' ) )
					);
					?>
				</p></div>
			<?php elseif ( empty( $pages ) ) : ?>
				<div class="notice notice-success inline"><p><?php esc_html_e( 'No high-impression, low-CTR pages found above the threshold. Topicality looks healthy.', 'platform-helm' ); ?></p></div>
			<?php else : ?>
				<div class="ph-gsc-card">
					<table class="widefat striped ph-gsc-table">
						<thead><tr>
							<th><?php esc_html_e( 'Page', 'platform-helm' ); ?></th>
							<th><?php esc_html_e( 'Impr.', 'platform-helm' ); ?></th>
							<th><?php esc_html_e( 'CTR', 'platform-helm' ); ?></th>
							<th><?php esc_html_e( 'Pos.', 'platform-helm' ); ?></th>
							<th><?php esc_html_e( 'Ranking for', 'platform-helm' ); ?></th>
						</tr></thead>
						<tbody>
							<?php foreach ( $pages as $p ) : ?>
								<?php
								$pid     = (int) $p['post_id'];
								$queries = $top_q[ $pid ] ?? [];
								$flagged = false;
								?>
								<tr>
									<td>
										<a href="<?php echo esc_url( (string) get_edit_post_link( $pid ) ); ?>"><?php echo esc_html( get_the_title( $pid ) ?: (string) $p['url'] ); ?></a>
										<div style="font-size:11px; color:#646970;"><?php echo esc_html( (string) $p['url'] ); ?></div>
									</td>
									<td><?php echo esc_html( number_format_i18n( (int) $p['impressions'] ) ); ?></td>
									<td><?php echo esc_html( round( (float) $p['ctr'] * 100, 2 ) . '%' ); ?></td>
									<td><?php echo esc_html( number_format( (float) $p['position'], 1 ) ); ?></td>
									<td style="max-width:320px; font-size:12px;">
										<?php
										if ( empty( $queries ) ) {
											echo '<span class="ph-gsc-hint">' . esc_html__( 'no query data', 'platform-helm' ) . '</span>';
										} else {
											$bits = [];
											foreach ( $queries as $q ) {
												$is_off = $this->matches_offbrand( (string) $q['query'], $terms );
												$flagged = $flagged || $is_off;
												$bits[]  = $is_off
													? '<strong style="color:#b32d2e;">' . esc_html( (string) $q['query'] ) . '</strong>'
													: esc_html( (string) $q['query'] );
											}
											echo implode( ', ', $bits ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
										}
										?>
										<div style="margin-top:6px;">
											<?php if ( $flagged ) : ?>
												<span class="ph-gsc-pill" style="background:#fce4e4; color:#8a1f1f; padding:1px 7px; border-radius:10px; font-size:11px;"><?php esc_html_e( 'off-brand match — rewrite for on-topic intent or noindex', 'platform-helm' ); ?></span>
											<?php else : ?>
												<span class="ph-gsc-hint" style="font-size:11px;"><?php esc_html_e( 'low CTR — check title/meta or intent', 'platform-helm' ); ?></span>
											<?php endif; ?>
											&nbsp;<a href="<?php echo esc_url( $auditor ); ?>"><?php esc_html_e( 'act in Auditor →', 'platform-helm' ); ?></a>
											<?php if ( $ai_on ) : ?>
												&nbsp;<button type="button" class="button button-small ph-offbrand-verdict" data-id="<?php echo esc_attr( (string) $pid ); ?>"><?php esc_html_e( 'AI verdict', 'platform-helm' ); ?></button>
											<?php endif; ?>
										</div>
										<?php if ( $ai_on ) : ?><div class="ph-offbrand-advice" style="display:none; white-space:pre-wrap; line-height:1.5; background:#f3fbf5; border:1px solid #d8ecdd; border-radius:6px; padding:10px; margin-top:6px; font-size:12px;"></div><?php endif; ?>
									</td>
								</tr>
							<?php endforeach; ?>
						</tbody>
					</table>
					<p class="ph-gsc-hint"><?php esc_html_e( 'Playbook: high real volume but off-intent → rewrite around your intended use case. Brand/partner pages that will never convert → noindex. Thin duplicates → 301 redirect to the stronger page. All three actions are in the Content Auditor.', 'platform-helm' ); ?></p>
				</div>
				<?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( $verdict_nonce ); ?>;
					document.addEventListener( 'click', function ( e ) {
						var btn = ( e.target && e.target.closest ) ? e.target.closest( '.ph-offbrand-verdict' ) : null;
						if ( ! btn ) { return; }
						e.preventDefault();
						var box = btn.closest( 'tr' ).querySelector( '.ph-offbrand-advice' );
						if ( ! box ) { return; }
						box.style.display = ''; box.textContent = 'Assessing intent…';
						btn.disabled = true; var lbl = btn.textContent; btn.textContent = 'Thinking…';
						var body = new URLSearchParams();
						body.append( 'action', 'platform_helm_offbrand_verdict' );
						body.append( 'nonce', nonce );
						body.append( 'post_id', btn.getAttribute( 'data-id' ) );
						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.verdict : ( ( res && res.data && res.data.message ) ? res.data.message : 'Could not generate a verdict.' );
							} )
							.catch( function () { btn.disabled = false; btn.textContent = lbl; box.textContent = 'Request failed — try again.'; } );
					} );
				}() );
				</script>
				<?php endif; ?>
			<?php endif; ?>
		</div>
		<?php
	}
}
