<?php
/**
 * Admin UI — Link Architect.
 *
 * Renders the Tools → Link Architect submenu page with three tabs:
 *   1. Scan & Suggest — paste orphans, get grouped suggestions
 *   2. Execute        — process approved suggestions in batches
 *   3. Restore        — roll back any modified post
 *
 * Master-admin gated. CSS inline; JS loaded from assets/js/link-architect.js.
 *
 * @package Platform\Helm\Modules\Link_Architect
 */

declare( strict_types = 1 );

namespace Platform\Helm\Modules\Link_Architect;

use Platform\Helm\Core\Assets;

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

final class AdminUI {

	public const PAGE_SLUG   = 'platform-helm-link-architect';
	public const PARENT_SLUG = 'platform-helm';

	/** Bulk content modification is an editor-level action, not just super-admin. */
	private const CAPABILITY = 'edit_others_posts';

	/** Hook suffix returned by add_submenu_page — used to scope script enqueueing. */
	private string $hook_suffix = '';

	public function __construct(
		private Assets $assets,
		private BackupManager $backups
	) {}

	public function register_hooks(): void {
		add_action( 'admin_menu', [ $this, 'register_menu' ], 25 );
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
	}

	public function register_menu(): void {
		Diagnostics::checkpoint( 'register_menu() called' );
		if ( ! current_user_can( self::CAPABILITY ) ) {
			Diagnostics::checkpoint( 'register_menu: NOT master admin, returning early' );
			return;
		}
		Diagnostics::checkpoint( 'register_menu: is master admin, calling add_submenu_page' );
		$hook = add_submenu_page(
			self::PARENT_SLUG,
			__( 'Link Architect', 'platform-helm' ),
			__( 'Link Architect', 'platform-helm' ),
			self::CAPABILITY,
			self::PAGE_SLUG,
			[ $this, 'render' ]
		);
		Diagnostics::checkpoint( 'register_menu: add_submenu_page returned', $hook );
		if ( false !== $hook ) {
			$this->hook_suffix = (string) $hook;
		}
	}

	public function enqueue_assets( string $hook ): void {
		// Hook-suffix match is most reliable — sanitisation of parent slugs
		// makes "{parent}_page_{slug}" prediction fragile.
		if ( '' === $this->hook_suffix || $hook !== $this->hook_suffix ) {
			return;
		}
		$this->assets->enqueue_link_architect( [
			'ajaxUrl' => admin_url( 'admin-ajax.php' ),
			'nonce'   => wp_create_nonce( AjaxController::NONCE_ACTION ),
			'i18n'    => [
				'scanning'   => __( 'Scanning…', 'platform-helm' ),
				'noResults'  => __( 'No suggestions found.', 'platform-helm' ),
				'noApproved' => __( 'No suggestions are approved yet. Approve some in Tab 1 first.', 'platform-helm' ),
				'starting'   => __( 'Starting…', 'platform-helm' ),
				'done'       => __( 'Done.', 'platform-helm' ),
				'matches'    => __( 'matches', 'platform-helm' ),
				'confirmRestore' => __( 'Restore this post to its pre-modification state?', 'platform-helm' ),
				'confirmDelete'  => __( 'Delete this backup? The post will keep its current state.', 'platform-helm' ),
			],
		] );
	}

	public function render(): void {
		// Always register shutdown handler FIRST so even uncatchable fatals
		// later in the request get a log entry.
		Diagnostics::register_shutdown();
		Diagnostics::checkpoint( '=== render() entered ===' );

		try {
			Diagnostics::checkpoint( 'about to check auth' );
			if ( ! current_user_can( self::CAPABILITY ) ) {
				wp_die( esc_html__( 'You do not have permission to access this page.', 'platform-helm' ) );
			}
			Diagnostics::checkpoint( 'auth passed' );

			$active_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( (string) $_GET['tab'] ) ) : 'scan';
			Diagnostics::checkpoint( 'active_tab resolved', $active_tab );

			// Special diagnostics tab — view the log inline. Useful if the
			// main render fails before we can show a panel.
			if ( 'diagnostics' === $active_tab ) {
				$this->render_diagnostics_view();
				return;
			}

			$base_url = admin_url( 'admin.php?page=' . self::PAGE_SLUG );
			Diagnostics::checkpoint( 'base_url built', $base_url );

			Diagnostics::checkpoint( 'about to emit CSS' );
			echo $this->inline_css(); // phpcs:ignore WordPress.Security.EscapeOutput
			Diagnostics::checkpoint( 'CSS emitted' );

			Diagnostics::checkpoint( 'about to render header' );
			?>
			<div class="wrap ph-wrap platform-helm-la-wrap phla-wrap">
				<h1 class="ph-h1">
					<?php esc_html_e( 'Link Architect', 'platform-helm' ); ?>
					<span class="ph-emoji">🏗</span>
				</h1>
				<p class="ph-sub">
					<?php esc_html_e( 'Bulk-build internal links pointing TO orphan pages FROM existing published content. The opposite of Link Assassin.', 'platform-helm' ); ?>
				</p>

				<nav class="ph-tabs">
					<a class="ph-tab <?php echo 'scan' === $active_tab ? 'ph-tab--active' : ''; ?>"
					   href="<?php echo esc_url( add_query_arg( 'tab', 'scan', $base_url ) ); ?>">
						<?php esc_html_e( '1. Scan & Suggest', 'platform-helm' ); ?>
					</a>
					<a class="ph-tab <?php echo 'execute' === $active_tab ? 'ph-tab--active' : ''; ?>"
					   href="<?php echo esc_url( add_query_arg( 'tab', 'execute', $base_url ) ); ?>">
						<?php esc_html_e( '2. Execute', 'platform-helm' ); ?>
					</a>
					<a class="ph-tab <?php echo 'restore' === $active_tab ? 'ph-tab--active' : ''; ?>"
					   href="<?php echo esc_url( add_query_arg( 'tab', 'restore', $base_url ) ); ?>">
						<?php esc_html_e( '3. Restore', 'platform-helm' ); ?>
					</a>
				</nav>
				<?php Diagnostics::checkpoint( 'header + tabs rendered' ); ?>

				<div class="ph-card">
					<?php
					Diagnostics::checkpoint( 'about to render tab', $active_tab );
					switch ( $active_tab ) {
						case 'execute':
							$this->render_execute_tab();
							break;
						case 'restore':
							$this->render_restore_tab();
							break;
						default:
							$this->render_scan_tab();
							break;
					}
					Diagnostics::checkpoint( 'tab rendered', $active_tab );
					?>
				</div>
			</div>
			<?php
			Diagnostics::checkpoint( '=== render() completed normally ===' );
		} catch ( \Throwable $e ) {
			Diagnostics::log_exception( $e );
			$this->render_error_panel( $e );
		}
	}

	/**
	 * Visible inline error panel showing exactly what failed and where.
	 */
	private function render_error_panel( \Throwable $e ): void {
		$diag_url = admin_url( 'admin.php?page=' . self::PAGE_SLUG . '&tab=diagnostics' );
		?>
		<div class="wrap ph-wrap">
			<h1 style="color:#c53030;">Link Architect — Diagnostic Output</h1>
			<div style="background:#fff;border:2px solid #c53030;padding:20px;margin:20px 0;border-radius:6px;">
				<h2 style="color:#c53030;margin-top:0;">⚠ Error caught</h2>
				<table style="width:100%;border-collapse:collapse;">
					<tr><td style="padding:6px;font-weight:600;width:120px;vertical-align:top;">Class:</td>
						<td style="padding:6px;"><code><?php echo esc_html( get_class( $e ) ); ?></code></td></tr>
					<tr><td style="padding:6px;font-weight:600;vertical-align:top;">Message:</td>
						<td style="padding:6px;"><code><?php echo esc_html( $e->getMessage() ); ?></code></td></tr>
					<tr><td style="padding:6px;font-weight:600;vertical-align:top;">File:</td>
						<td style="padding:6px;"><code><?php echo esc_html( $e->getFile() ); ?>:<?php echo (int) $e->getLine(); ?></code></td></tr>
				</table>
				<details open style="margin-top:14px;">
					<summary style="cursor:pointer;font-weight:600;">Stack trace</summary>
					<pre style="overflow:auto;background:#f5f5f5;padding:12px;font-size:11px;border-radius:4px;line-height:1.5;"><?php
						echo esc_html( $e->getTraceAsString() );
					?></pre>
				</details>
			</div>

			<div style="background:#fff;border:1px solid #ccc;padding:20px;margin:20px 0;border-radius:6px;">
				<h2>Checkpoints reached before failure</h2>
				<p style="color:#666;font-size:12px;">The last checkpoint is roughly where the failure occurred.</p>
				<ol style="font-family:ui-monospace,monospace;font-size:12px;line-height:1.7;">
					<?php foreach ( Diagnostics::get_checkpoints() as $cp ) : ?>
						<li>
							<strong><?php echo esc_html( (string) $cp['msg'] ); ?></strong>
							<?php if ( isset( $cp['data'] ) && null !== $cp['data'] ) : ?>
								— <code style="background:#eef;padding:1px 6px;border-radius:3px;"><?php
									echo esc_html(
										is_string( $cp['data'] )
											? $cp['data']
											: (string) wp_json_encode( $cp['data'] )
									);
								?></code>
							<?php endif; ?>
						</li>
					<?php endforeach; ?>
				</ol>
			</div>

			<div style="margin-top:20px;font-size:13px;">
				<p><strong>Full diagnostic log:</strong> <code><?php echo esc_html( Diagnostics::log_file() ); ?></code></p>
				<p><a href="<?php echo esc_url( $diag_url ); ?>" class="button">View full log inline</a></p>
			</div>
		</div>
		<?php
	}

	/**
	 * Diagnostics tab — pure log viewer. Doesn't touch any other module
	 * code so it works even when the main render is broken.
	 */
	private function render_diagnostics_view(): void {
		if ( isset( $_GET['clear'] ) && '1' === $_GET['clear'] ) {
			Diagnostics::clear_log();
			$clean = admin_url( 'admin.php?page=' . self::PAGE_SLUG . '&tab=diagnostics' );
			echo '<script>window.location.href=' . wp_json_encode( $clean ) . ';</script>';
			return;
		}
		$log_file = Diagnostics::log_file();
		$log_text = Diagnostics::read_log();
		?>
		<div class="wrap">
			<h1>Link Architect — Diagnostics Log</h1>
			<p>Reading: <code><?php echo esc_html( $log_file ); ?></code></p>
			<p>
				<a href="<?php echo esc_url( admin_url( 'admin.php?page=' . self::PAGE_SLUG . '&tab=diagnostics&clear=1' ) ); ?>" class="button">Clear log</a>
				<a href="<?php echo esc_url( admin_url( 'admin.php?page=' . self::PAGE_SLUG ) ); ?>" class="button">Back to Link Architect</a>
			</p>
			<pre style="background:#f5f5f5;border:1px solid #ccc;padding:15px;max-height:600px;overflow:auto;font-size:11px;line-height:1.5;border-radius:4px;"><?php echo esc_html( $log_text ); ?></pre>
		</div>
		<?php
	}

	private function render_scan_tab(): void {
		?>
		<h2 class="ph-h2"><?php esc_html_e( 'Paste orphan URLs', 'platform-helm' ); ?></h2>
		<p class="ph-help">
			<?php esc_html_e( "One per line. Each must be a URL on this site. We'll find the best phrase to use as anchor text, then list published posts where that phrase appears.", 'platform-helm' ); ?>
		</p>
		<textarea
			id="ph-orphans-input"
			rows="8"
			class="ph-textarea"
			placeholder="https://example.com/orphan-page-one&#10;https://example.com/orphan-page-two"></textarea>

		<script>
		/* Prefill orphan textarea when arriving via a "Fix this" deep link.
		 * Reads ?prefill_url=… (already sanitized server-side via PHP below).
		 * Decodes one URL per line; multiple ?prefill_url[]= entries supported. */
		(function () {
			var prefill = <?php echo wp_json_encode( $this->prefill_urls_from_request() ); ?>;
			if ( ! prefill || ! prefill.length ) { return; }
			var ta = document.getElementById( 'ph-orphans-input' );
			if ( ! ta ) { return; }
			ta.value = prefill.join( '\n' );
			ta.scrollIntoView( { behavior: 'smooth', block: 'center' } );
			ta.focus();
		})();
		</script>

		<div style="margin-top:12px;display:flex;gap:10px;align-items:center;">
			<button type="button" id="ph-scan-btn" class="ph-btn ph-btn--primary">
				<?php esc_html_e( 'Generate Suggestions', 'platform-helm' ); ?>
			</button>
			<span id="ph-scan-status" class="ph-status"></span>
		</div>

		<div id="ph-results" style="margin-top:20px;"></div>

		<div id="ph-approve-bar" class="ph-approve-bar" style="display:none;">
			<span id="ph-approved-count">0</span> <?php esc_html_e( 'suggestions approved.', 'platform-helm' ); ?>
			<button type="button" id="ph-save-approvals-btn" class="ph-btn ph-btn--primary">
				<?php esc_html_e( 'Save & go to Execute →', 'platform-helm' ); ?>
			</button>
		</div>
		<?php
	}

	private function render_execute_tab(): void {
		?>
		<h2 class="ph-h2"><?php esc_html_e( 'Execute approved suggestions', 'platform-helm' ); ?></h2>
		<p class="ph-help">
			<?php esc_html_e( 'Processes 25 source posts at a time. Each modified post is backed up first. Safety caps: maximum 3 new outbound internal links per source post per execution. Posts already linking to the orphan are skipped automatically.', 'platform-helm' ); ?>
		</p>

		<div style="margin-top:12px;display:flex;gap:10px;align-items:center;">
			<button type="button" id="ph-execute-btn" class="ph-btn ph-btn--danger">
				<?php esc_html_e( 'Execute approved links', 'platform-helm' ); ?>
			</button>
			<span id="ph-execute-status" class="ph-status"></span>
		</div>

		<div id="ph-progress-wrap" style="display:none;margin-top:18px;">
			<div class="ph-progress-track">
				<div id="ph-progress-fill" class="ph-progress-fill" style="width:0%;"></div>
			</div>
			<div id="ph-progress-counts" class="ph-progress-counts"></div>
		</div>

		<div id="ph-execute-log" class="ph-log" style="margin-top:20px;"></div>
		<?php
	}

	private function render_restore_tab(): void {
		$backups = $this->backups->list_all();
		$count   = count( $backups );
		?>
		<h2 class="ph-h2"><?php esc_html_e( 'Restore', 'platform-helm' ); ?></h2>
		<p class="ph-help">
			<?php
			echo esc_html( sprintf(
				/* translators: %d: count */
				_n( '%d backup on record. Showing newest first.', '%d backups on record. Showing newest first.', $count, 'platform-helm' ),
				$count
			) );
			?>
		</p>

		<?php if ( 0 === $count ) : ?>
			<div class="ph-empty">
				<div class="ph-empty-icon">📁</div>
				<div class="ph-empty-title"><?php esc_html_e( 'No backups yet.', 'platform-helm' ); ?></div>
				<div class="ph-empty-detail"><?php esc_html_e( 'Backups appear here after Link Architect modifies a post in the Execute tab.', 'platform-helm' ); ?></div>
			</div>
			<?php return; ?>
		<?php endif; ?>

		<table class="ph-table">
			<thead>
				<tr>
					<th><?php esc_html_e( 'Source Post', 'platform-helm' ); ?></th>
					<th><?php esc_html_e( 'Phrase linked', 'platform-helm' ); ?></th>
					<th><?php esc_html_e( 'Linked to', 'platform-helm' ); ?></th>
					<th><?php esc_html_e( 'When', 'platform-helm' ); ?></th>
					<th><?php esc_html_e( 'Actions', 'platform-helm' ); ?></th>
				</tr>
			</thead>
			<tbody>
				<?php foreach ( $backups as $b ) : ?>
					<tr data-post-id="<?php echo esc_attr( (string) $b['post_id'] ); ?>"
					    data-meta-key="<?php echo esc_attr( (string) $b['meta_key'] ); ?>">
						<td>
							<a href="<?php echo esc_url( (string) get_edit_post_link( (int) $b['post_id'] ) ); ?>">
								<?php echo esc_html( (string) $b['post_title'] ); ?>
							</a>
							<div class="ph-pid">ID: <?php echo esc_html( (string) $b['post_id'] ); ?></div>
						</td>
						<td><code class="ph-phrase"><?php echo esc_html( (string) $b['phrase'] ); ?></code></td>
						<td>
							<?php if ( '' !== $b['orphan_url'] ) : ?>
								<a href="<?php echo esc_url( (string) $b['orphan_url'] ); ?>" target="_blank" rel="noopener" class="ph-link">
									<?php echo esc_html( $this->shorten_url( (string) $b['orphan_url'] ) ); ?>
								</a>
							<?php else : ?>
								—
							<?php endif; ?>
						</td>
						<td>
							<?php
							$ts = (int) $b['timestamp'];
							echo esc_html( $ts > 0 ? wp_date( 'jS M Y g:i a', $ts ) : '—' );
							?>
						</td>
						<td>
							<button type="button" class="ph-btn ph-btn--small ph-restore-btn">
								<?php esc_html_e( 'Restore', 'platform-helm' ); ?>
							</button>
							<button type="button" class="ph-btn ph-btn--small ph-btn--danger-outline ph-delete-btn">
								<?php esc_html_e( 'Delete backup', 'platform-helm' ); ?>
							</button>
						</td>
					</tr>
				<?php endforeach; ?>
			</tbody>
		</table>
		<?php
	}

	private function shorten_url( string $url ): string {
		$parsed = wp_parse_url( $url );
		$path   = (string) ( $parsed['path'] ?? '' );
		if ( '' !== $path && strlen( $path ) > 30 ) {
			return '…' . substr( $path, -28 );
		}
		return '' !== $path ? $path : $url;
	}

	private function inline_css(): string {
		ob_start();
		?>
<style id="platform-helm-link-architect-css">
.phla-wrap {
	--ph-brand: #0047AB;
	--ph-brand-2: #2C6BD9;
	--ph-brand-tint: #EAF1FB;
	--ph-brand-dark: #003680;
	--ph-text: #0F1A33;
	--ph-text-2: #5C6B82;
	--ph-text-muted: #8A95A8;
	--ph-bg: #FFFFFF;
	--ph-bg-soft: #F7F9FC;
	--ph-border: #E5EAF2;
	--ph-success: #1F8A5A;
	--ph-success-tint: #E4F7EE;
	--ph-warning: #B7791F;
	--ph-warning-tint: #FFF6E0;
	--ph-danger: #C53030;
	--ph-danger-tint: #FFE8E8;

	max-width: 1300px;
	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, "Helvetica Neue", Arial, sans-serif;
	color: var(--ph-text);
}
.phla-wrap * { box-sizing: border-box; }

.ph-h1 { font-size: 24px; font-weight: 700; letter-spacing: -0.4px; margin: 12px 0 4px; display: flex; align-items: center; gap: 10px; }
.ph-h1 .ph-emoji { font-size: 22px; }
.ph-sub { margin: 0 0 18px; font-size: 13px; color: var(--ph-text-2); }
.ph-h2 { margin: 0 0 8px; font-size: 16px; font-weight: 700; color: var(--ph-text); }
.ph-help { margin: 0 0 14px; font-size: 13px; color: var(--ph-text-2); }

.ph-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--ph-border); margin-bottom: 0; }
.ph-tab {
	padding: 10px 18px; font-size: 13px; font-weight: 600; text-decoration: none;
	color: var(--ph-text-2); background: transparent; border: 1px solid transparent;
	border-bottom: 0; border-radius: 6px 6px 0 0; transition: background 120ms, color 120ms;
	margin-bottom: -1px;
}
.ph-tab:hover { color: var(--ph-text); background: var(--ph-bg-soft); }
.ph-tab--active { color: var(--ph-brand); background: var(--ph-bg); border-color: var(--ph-border); }

.ph-card {
	background: var(--ph-bg); border: 1px solid var(--ph-border);
	border-top: 0; border-radius: 0 8px 8px 8px;
	padding: 22px 24px; box-shadow: 0 1px 2px rgba(15,26,51,0.04);
}

.ph-textarea {
	width: 100%; padding: 10px 12px; font-family: ui-monospace, "SF Mono", Menlo, monospace;
	font-size: 12px; line-height: 1.5; color: var(--ph-text);
	border: 1px solid var(--ph-border); border-radius: 6px; background: var(--ph-bg-soft);
}
.ph-textarea:focus { outline: 2px solid var(--ph-brand); outline-offset: -1px; background: var(--ph-bg); }

.ph-btn {
	padding: 7px 16px; font-size: 12px; font-weight: 600; border-radius: 6px; cursor: pointer;
	border: 1px solid var(--ph-border); background: var(--ph-bg); color: var(--ph-text);
	transition: all 120ms;
}
.ph-btn:hover:not([disabled]) { background: var(--ph-bg-soft); }
.ph-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.ph-btn--primary { background: var(--ph-brand); color: #fff; border-color: var(--ph-brand); }
.ph-btn--primary:hover:not([disabled]) { background: var(--ph-brand-dark); border-color: var(--ph-brand-dark); }
.ph-btn--danger { background: var(--ph-danger); color: #fff; border-color: var(--ph-danger); }
.ph-btn--danger:hover:not([disabled]) { background: #A52020; border-color: #A52020; }
.ph-btn--danger-outline { color: var(--ph-danger); border-color: var(--ph-danger-tint); }
.ph-btn--danger-outline:hover:not([disabled]) { background: var(--ph-danger-tint); }
.ph-btn--small { padding: 4px 10px; font-size: 11px; }

.ph-status { font-size: 12px; color: var(--ph-text-2); font-weight: 500; }

.ph-orphan-group {
	background: var(--ph-bg); border: 1px solid var(--ph-border); border-radius: 8px;
	margin-bottom: 12px; overflow: hidden;
}
.ph-orphan-header {
	padding: 12px 16px; background: var(--ph-bg-soft); border-bottom: 1px solid var(--ph-border);
	display: flex; justify-content: space-between; align-items: center; gap: 12px;
}
.ph-orphan-url {
	font-family: ui-monospace, monospace; font-size: 12px; color: var(--ph-brand);
	white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; min-width: 0;
	text-decoration: none; font-weight: 600;
}
.ph-orphan-meta { font-size: 11px; color: var(--ph-text-muted); white-space: nowrap; }
.ph-candidate-select {
	font-size: 12px; padding: 4px 8px; border: 1px solid var(--ph-border); border-radius: 4px;
	background: var(--ph-bg); color: var(--ph-text);
}
.ph-group-actions { display: flex; gap: 6px; align-items: center; }
.ph-orphan-body { padding: 0; }
.ph-error {
	padding: 12px 16px; font-size: 12px; color: var(--ph-danger); background: var(--ph-danger-tint);
}
.ph-suggestion-list { list-style: none; margin: 0; padding: 0; }
.ph-suggestion {
	display: flex; align-items: center; gap: 12px; padding: 10px 16px;
	border-bottom: 1px solid var(--ph-border); font-size: 12px;
}
.ph-suggestion:last-child { border-bottom: 0; }
.ph-suggestion:hover { background: var(--ph-bg-soft); }
.ph-suggestion input[type=checkbox] { margin: 0; flex-shrink: 0; }
.ph-suggestion-main { flex: 1; min-width: 0; }
.ph-suggestion-title { font-weight: 600; color: var(--ph-text); }
.ph-suggestion-title a { color: var(--ph-brand); text-decoration: none; }
.ph-suggestion-title a:hover { text-decoration: underline; }
.ph-suggestion-excerpt { color: var(--ph-text-muted); font-size: 11px; margin-top: 2px; line-height: 1.5; }
.ph-suggestion-type {
	display: inline-block; padding: 1px 6px; font-size: 10px; font-weight: 600;
	background: var(--ph-brand-tint); color: var(--ph-brand);
	border-radius: 3px; text-transform: uppercase; letter-spacing: 0.3px;
	margin-left: 6px;
}

.ph-approve-bar {
	position: sticky; bottom: 20px; margin-top: 20px;
	padding: 12px 18px; background: var(--ph-text); color: #fff;
	border-radius: 8px; display: flex; justify-content: space-between; align-items: center;
	box-shadow: 0 6px 18px rgba(15,26,51,0.2);
}

.ph-progress-track {
	height: 10px; background: var(--ph-bg-soft); border: 1px solid var(--ph-border);
	border-radius: 999px; overflow: hidden;
}
.ph-progress-fill {
	height: 100%; background: linear-gradient(90deg, var(--ph-brand) 0%, var(--ph-brand-2) 100%);
	border-radius: 999px; transition: width 400ms cubic-bezier(0.4,0,0.2,1);
}
.ph-progress-counts { margin-top: 8px; font-size: 12px; color: var(--ph-text-2); }
.ph-progress-counts strong { color: var(--ph-text); font-weight: 700; font-variant-numeric: tabular-nums; }
.ph-progress-counts .ph-ok   { color: var(--ph-success); }
.ph-progress-counts .ph-skip { color: var(--ph-warning); }
.ph-progress-counts .ph-fail { color: var(--ph-danger); }

.ph-log {
	max-height: 320px; overflow-y: auto;
	background: var(--ph-bg-soft); border: 1px solid var(--ph-border); border-radius: 6px;
}
.ph-log-row {
	padding: 8px 12px; font-size: 11px; border-bottom: 1px solid var(--ph-border);
	display: flex; gap: 10px; align-items: center;
}
.ph-log-row:last-child { border-bottom: 0; }
.ph-log-tag {
	flex-shrink: 0; padding: 2px 7px; border-radius: 4px;
	font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.3px;
}
.ph-log-tag--success { background: var(--ph-success-tint); color: var(--ph-success); }
.ph-log-tag--skipped { background: var(--ph-warning-tint); color: var(--ph-warning); }
.ph-log-tag--failed  { background: var(--ph-danger-tint);  color: var(--ph-danger); }

.ph-table {
	width: 100%; border-collapse: collapse; background: var(--ph-bg);
	border: 1px solid var(--ph-border); border-radius: 8px; overflow: hidden;
}
.ph-table thead th {
	padding: 10px 14px; text-align: left; font-size: 11px; font-weight: 600;
	text-transform: uppercase; letter-spacing: 0.5px; color: var(--ph-text-2);
	background: var(--ph-bg-soft); border-bottom: 1px solid var(--ph-border);
}
.ph-table tbody td {
	padding: 12px 14px; font-size: 13px; border-bottom: 1px solid var(--ph-border);
	vertical-align: top;
}
.ph-table tbody tr:last-child td { border-bottom: 0; }
.ph-table tbody tr:hover { background: var(--ph-bg-soft); }
.ph-table .ph-pid { font-size: 11px; color: var(--ph-text-muted); margin-top: 2px; }
.ph-phrase {
	font-family: ui-monospace, monospace; font-size: 11px;
	background: var(--ph-brand-tint); color: var(--ph-brand-dark);
	padding: 2px 6px; border-radius: 4px;
}
.ph-link { color: var(--ph-brand); text-decoration: none; font-family: ui-monospace, monospace; font-size: 11px; }
.ph-link:hover { text-decoration: underline; }

.ph-empty {
	padding: 32px; text-align: center;
	background: var(--ph-bg-soft); border: 1px dashed var(--ph-border); border-radius: 8px;
}
.ph-empty-icon { font-size: 32px; opacity: 0.6; margin-bottom: 6px; }
.ph-empty-title { font-size: 14px; font-weight: 600; color: var(--ph-text); margin-bottom: 4px; }
.ph-empty-detail { font-size: 12px; color: var(--ph-text-2); }
</style>
		<?php
		return (string) ob_get_clean();
	}

	/**
	 * Extract and sanitise URLs from the ?prefill_url query param.
	 *
	 * Used by the "Fix this" deep-link from the Internal Link Health orphans
	 * report — clicking it loads this page with the orphan URL pre-filled in
	 * the scan textarea. Accepts a single string or an array. Each URL is
	 * passed through esc_url_raw and constrained to this site's host so we
	 * never pre-fill an external URL someone hand-crafted.
	 *
	 * @return string[]
	 */
	private function prefill_urls_from_request(): array {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( empty( $_GET['prefill_url'] ) ) {
			return [];
		}
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$raw = wp_unslash( $_GET['prefill_url'] );
		$raw = is_array( $raw ) ? $raw : [ $raw ];

		$site_host = (string) wp_parse_url( home_url(), PHP_URL_HOST );
		$out       = [];
		foreach ( $raw as $candidate ) {
			$url = esc_url_raw( (string) $candidate );
			if ( '' === $url ) {
				continue;
			}
			$host = (string) wp_parse_url( $url, PHP_URL_HOST );
			if ( '' === $host || $host !== $site_host ) {
				continue; // Only allow same-site URLs.
			}
			$out[] = $url;
			if ( count( $out ) >= 25 ) {
				break; // Hard cap on prefilled batch size.
			}
		}
		return array_values( array_unique( $out ) );
	}
}
