<?php
/**
 * Dashboard Page — the Platform Helm command center.
 *
 * The top-level landing page. In Phase 1 it shows:
 *   - Stat cards (modules enabled / total, and placeholders that later phases
 *     fill with linking metrics).
 *   - A "getting started" strip when no modules are enabled yet.
 *   - A module status grid with per-module on/off state and quick links.
 *
 * Later phases extend get_pulse_data() with real linking signals (orphan
 * count, suggestions available, internal-link health) once those modules ship.
 *
 * SECURITY: gated by `manage_options`. No master-email concept.
 *
 * PERFORMANCE: all computed data is cached in a single transient for 5 minutes;
 * module iteration is in-memory and free.
 *
 * @package Platform\Helm\Admin
 */

declare( strict_types = 1 );

namespace Platform\Helm\Admin;

use Platform\Helm\Core\ModuleRegistry;
use Platform\Helm\Core\Settings;
use Platform\Helm\Core\Ui;

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

final class DashboardPage {

	public const PAGE_SLUG = 'platform-helm';

	private const CAPABILITY = 'manage_options';

	/** Base64 data-URI menu icon (the helm wheel). Inlined so it needs no HTTP request and renders on any host. */

	private const TRANSIENT_KEY = 'platform_helm_pulse_v1';
	private const TRANSIENT_TTL = 300; // 5 minutes.

	private const REFRESH_QUERY_VAR    = 'platform_helm_pulse_refresh';
	private const REFRESH_NONCE_ACTION = 'platform_helm_pulse_refresh_action';

	public function __construct(
		private Settings $settings,
		private ModuleRegistry $registry
	) {}

	public function register_hooks(): void {
		add_action( 'admin_menu', [ $this, 'register_menu' ], 9 );
		add_action( 'admin_head', [ $this, 'hide_submenu_css' ] );
		add_action( 'admin_init', [ $this, 'maybe_handle_refresh' ] );
	}

	/**
	 * Keep the Helm sidebar short without breaking page access. Only Dashboard,
	 * Modules, Roles & Menus and Tools stay visible; every other module page is
	 * hidden from the submenu with CSS while remaining fully registered — so it
	 * still passes WordPress's access check and is reachable by URL and via the
	 * "View module" links on the Modules page.
	 *
	 * (We deliberately do NOT use remove_submenu_page(): that drops the page from
	 * $submenu, which makes user_can_access_admin_page() deny it — the "Sorry,
	 * you are not allowed to access this page" error.)
	 */
	public function hide_submenu_css(): void {
		global $submenu;
		if ( empty( $submenu[ self::PAGE_SLUG ] ) || ! is_array( $submenu[ self::PAGE_SLUG ] ) ) {
			return;
		}
		$keep = [
			self::PAGE_SLUG,            // Dashboard (collapses with parent).
			'platform-helm-modules',   // Modules.
			'platform-helm-roles',     // Roles & Menus.
			'platform-helm-tools',     // Tools.
		];
		$selectors = [];
		foreach ( $submenu[ self::PAGE_SLUG ] as $item ) {
			$slug = sanitize_key( $item[2] ?? '' );
			if ( '' !== $slug && ! in_array( $slug, $keep, true ) ) {
				$selectors[] = '#adminmenu .wp-submenu li:has(> a[href$="page=' . $slug . '"])';
			}
		}
		if ( empty( $selectors ) ) {
			return;
		}
		echo '<style id="platform-helm-hide-submenu">' . implode( ',', $selectors ) . '{display:none!important;}</style>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- slugs are sanitize_key()'d (a-z0-9_-).
	}

	/* ===================================================================== *
	 * MENU
	 * ===================================================================== */

	public function register_menu(): void {
		if ( ! current_user_can( self::CAPABILITY ) ) {
			return;
		}
		add_menu_page(
			__( 'Platform Helm', 'platform-helm' ),
			__( 'Helm', 'platform-helm' ),
			self::CAPABILITY,
			self::PAGE_SLUG,
			[ $this, 'render' ],
			\Platform\Helm\Core\Ui::logo_menu_icon(),
			80
		);
		// Dashboard submenu collapses with the parent (standard WP pattern).
		add_submenu_page(
			self::PAGE_SLUG,
			__( 'Dashboard', 'platform-helm' ),
			__( 'Dashboard', 'platform-helm' ),
			self::CAPABILITY,
			self::PAGE_SLUG,
			[ $this, 'render' ]
		);
	}

	/* ===================================================================== *
	 * REFRESH
	 * ===================================================================== */

	public function maybe_handle_refresh(): void {
		if ( ! isset( $_GET[ self::REFRESH_QUERY_VAR ] ) ) {
			return;
		}
		if ( ! current_user_can( self::CAPABILITY ) ) {
			return;
		}
		check_admin_referer( self::REFRESH_NONCE_ACTION );
		delete_transient( self::TRANSIENT_KEY );
		wp_safe_redirect( admin_url( 'admin.php?page=' . self::PAGE_SLUG ) );
		exit;
	}

	/* ===================================================================== *
	 * RENDER
	 * ===================================================================== */

	public function render(): void {
		if ( ! current_user_can( self::CAPABILITY ) ) {
			wp_die( esc_html__( 'You do not have permission to access this page.', 'platform-helm' ) );
		}

		$pulse = $this->get_pulse_data();

		$refresh_url = wp_nonce_url(
			add_query_arg( self::REFRESH_QUERY_VAR, '1', admin_url( 'admin.php?page=' . self::PAGE_SLUG ) ),
			self::REFRESH_NONCE_ACTION
		);

		$age      = (int) ( $pulse['generated_at'] ?? 0 );
		$age_text = $age > 0
			/* translators: %s: human-readable time difference, e.g. "5 mins" */
			? sprintf( __( 'Updated %s ago', 'platform-helm' ), human_time_diff( $age, time() ) )
			: __( 'Updated just now', 'platform-helm' );

		$subtitle = __( 'Your content operations, in one screen.', 'platform-helm' ) . ' · ' . $age_text;
		$actions  = '<a href="' . esc_url( $refresh_url ) . '" class="btn btn-primary"><i class="ti ti-refresh"></i> ' . esc_html__( 'Refresh', 'platform-helm' ) . '</a>';

		Ui::open_page( __( 'Command Center', 'platform-helm' ), $subtitle, $actions );

		$this->render_stat_cards( $pulse );
		$this->render_getting_started( $pulse ); // Full-width welcome notice (empty installs only).

		// Working area, mirroring the design's Command Center (minus the module
		// list). The setup checklist only shows until the site is set up, so we
		// place it in the right column when present and otherwise let the health
		// + activity column span the full width — never an empty column.
		ob_start();
		$this->render_setup_checklist( $pulse );
		$right = trim( (string) ob_get_clean() );

		ob_start();
		$this->render_health_overview( $pulse );
		$this->render_activity_feed( $pulse );
		$left = (string) ob_get_clean();

		$col = 'display:flex;flex-direction:column;gap:16px';
		if ( '' !== $right ) {
			// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- pre-escaped markup.
			echo '<div class="main-cols"><div style="' . $col . '">' . $left . '</div><div style="' . $col . '">' . $right . '</div></div>';
		} else {
			// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- pre-escaped markup.
			echo '<div style="' . $col . '">' . $left . '</div>';
		}

		Ui::close_page();
	}

	/**
	 * @param array<string,mixed> $pulse
	 */
	private function render_stat_cards( array $pulse ): void {
		$total_modules = (int) ( $pulse['total_modules']   ?? 0 );
		$enabled_count = (int) ( $pulse['enabled_modules'] ?? 0 );
		$orphan = $pulse['orphan_count'] ?? null;
		$links  = $pulse['total_links'] ?? null;
		$broken = $pulse['broken_links'] ?? null;
		?>
		<div class="stat-grid">
			<?php
			Ui::stat_tile(
				__( 'Modules Active', 'platform-helm' ),
				sprintf( '%d / %d', $enabled_count, $total_modules ),
				'blue',
				'ti-puzzle',
				__( 'enabled', 'platform-helm' ),
				'up'
			);
			Ui::stat_tile(
				__( 'Internal Links', 'platform-helm' ),
				null === $links ? '—' : number_format_i18n( (int) $links ),
				'green',
				'ti-link',
				__( 'across your content', 'platform-helm' ),
				'up'
			);
			Ui::stat_tile(
				__( 'Orphan Pages', 'platform-helm' ),
				null === $orphan ? '—' : number_format_i18n( (int) $orphan ),
				'amber',
				'ti-file-x',
				( null !== $orphan && (int) $orphan > 0 ) ? __( 'needs attention', 'platform-helm' ) : __( 'with no internal links', 'platform-helm' ),
				'warn',
				'amber'
			);
			Ui::stat_tile(
				__( 'Broken Links', 'platform-helm' ),
				null === $broken ? '—' : number_format_i18n( (int) $broken ),
				'red',
				'ti-unlink',
				( null !== $broken && (int) $broken > 0 ) ? __( 'fix these first', 'platform-helm' ) : __( 'none detected', 'platform-helm' ),
				( null !== $broken && (int) $broken > 0 ) ? 'danger' : '',
				( null !== $broken && (int) $broken > 0 ) ? 'red' : ''
			);
			?>
		</div>
		<?php
	}

	private function render_card( string $label, string $number, string $sub, string $tone ): void {
		// Legacy helper retained for back-compat; routes to the new stat tile.
		$map = [ 'neutral' => 'blue' ];
		Ui::stat_tile( $label, $number, $map[ $tone ] ?? 'blue', 'ti-chart-bar', $sub, '' );
	}

	/**
	 * Site Health Overview — the design's hero card: a score ring derived from
	 * real link-health signals plus a short issue list. When no Link Health data
	 * exists yet (module off or never scanned) it shows a scan prompt instead of
	 * a fabricated score.
	 *
	 * @param array<string,mixed> $pulse
	 */
	private function render_health_overview( array $pulse ): void {
		$orphan = $pulse['orphan_count'] ?? null;
		$links  = $pulse['total_links']  ?? null;
		$broken = $pulse['broken_links'] ?? null;

		$health_url = admin_url( 'admin.php?page=platform-helm-link-health' );

		Ui::card_open(
			__( 'Site Health Overview', 'platform-helm' ),
			'<a class="card-action" href="' . esc_url( $health_url ) . '">' . esc_html__( 'Full report', 'platform-helm' ) . ' →</a>',
			'ti-heart-rate-monitor'
		);

		// No real data to score against — prompt a scan rather than fake a number.
		if ( null === $orphan && null === $links && null === $broken ) {
			echo '<p class="text-secondary" style="margin-bottom:12px">' . esc_html__( 'Run a link-health scan to see your site’s health score and the issues worth fixing first.', 'platform-helm' ) . '</p>';
			echo '<a class="btn btn-primary btn-sm" href="' . esc_url( $health_url ) . '"><i class="ti ti-heart-rate-monitor" aria-hidden="true"></i> ' . esc_html__( 'Open Link Health', 'platform-helm' ) . '</a>';
			Ui::card_close();
			return;
		}

		$broken_n = (int) $broken;
		$orphan_n = (int) $orphan;
		$links_n  = (int) $links;

		// Honest score: broken links hurt most, orphans next. Bounded 0–100.
		$score = 100;
		$score -= min( 45, $broken_n * 5 );
		$score -= min( 35, $orphan_n );
		$score  = max( 0, $score );

		if ( $score >= 90 ) {
			$grade = __( 'Excellent', 'platform-helm' );
			$tone  = 'success';
			$head  = __( 'Your internal linking is in great shape', 'platform-helm' );
		} elseif ( $score >= 75 ) {
			$grade = __( 'Good', 'platform-helm' );
			$tone  = 'success';
			$head  = __( 'Content structure is healthy', 'platform-helm' );
		} elseif ( $score >= 55 ) {
			$grade = __( 'Fair', 'platform-helm' );
			$tone  = 'warning';
			$head  = __( 'A few things need attention', 'platform-helm' );
		} else {
			$grade = __( 'Needs work', 'platform-helm' );
			$tone  = 'danger';
			$head  = __( 'Several issues need fixing', 'platform-helm' );
		}

		// Ring geometry: circumference of r=32 ≈ 201; fill proportional to score.
		$circ   = 201;
		$filled = (int) round( $score / 100 * $circ );
		$gap    = $circ - $filled;
		?>
		<div class="flex gap-16 mb-16" style="padding-bottom:16px;border-bottom:1px solid var(--border)">
			<div class="health-ring-wrap">
				<svg width="80" height="80" viewBox="0 0 80 80" aria-hidden="true">
					<circle cx="40" cy="40" r="32" fill="none" stroke="var(--border)" stroke-width="8"></circle>
					<circle cx="40" cy="40" r="32" fill="none" stroke="var(--<?php echo esc_attr( $tone ); ?>)" stroke-width="8" stroke-dasharray="<?php echo esc_attr( $filled . ' ' . $gap ); ?>" stroke-linecap="round" transform="rotate(-90 40 40)"></circle>
				</svg>
				<div class="health-ring-label">
					<span class="health-score"><?php echo esc_html( (string) $score ); ?></span>
					<span class="health-grade"><?php echo esc_html( $grade ); ?></span>
				</div>
			</div>
			<div style="flex:1">
				<p style="font-size:13px;font-weight:700;color:var(--text-primary);margin-bottom:5px"><?php echo esc_html( $head ); ?></p>
				<p style="font-size:12px;color:var(--text-secondary);line-height:1.5">
					<?php
					echo esc_html(
						sprintf(
							/* translators: 1: internal link count, 2: orphan page count, 3: broken link count */
							__( '%1$s internal links across your content. %2$s orphan pages and %3$s broken links are the main items to address.', 'platform-helm' ),
							number_format_i18n( $links_n ),
							number_format_i18n( $orphan_n ),
							number_format_i18n( $broken_n )
						)
					);
					?>
				</p>
			</div>
		</div>
		<?php
		// Issue rows — each reflects real state, with a secondary cue (icon-dot + colour).
		$this->issue_row(
			$broken_n > 0 ? 'danger' : 'success',
			$broken_n > 0 ? __( 'Broken links detected', 'platform-helm' ) : __( 'No broken links', 'platform-helm' ),
			$broken_n,
			$health_url,
			$broken_n > 0 ? __( 'Fix', 'platform-helm' ) : ''
		);
		$this->issue_row(
			$orphan_n > 0 ? 'warning' : 'success',
			$orphan_n > 0 ? __( 'Orphan pages (no inbound links)', 'platform-helm' ) : __( 'No orphan pages', 'platform-helm' ),
			$orphan_n,
			$health_url,
			$orphan_n > 0 ? __( 'View', 'platform-helm' ) : ''
		);
		$this->issue_row(
			'success',
			__( 'Internal links across your content', 'platform-helm' ),
			$links_n,
			admin_url( 'admin.php?page=platform-helm-link-architect' ),
			__( 'Build', 'platform-helm' )
		);
		Ui::card_close();
	}

	/**
	 * One row in the Site Health Overview issue list.
	 */
	private function issue_row( string $tone, string $label, int $count, string $url, string $cta ): void {
		printf(
			'<div class="issue-row"><div class="issue-dot" style="background:var(--%1$s)"></div>'
			. '<span class="issue-label">%2$s</span>'
			. '<span class="issue-count" style="background:var(--%1$s-tint);color:var(--%1$s)">%3$s</span>',
			esc_attr( $tone ),
			esc_html( $label ),
			esc_html( number_format_i18n( $count ) )
		);
		if ( '' !== $cta ) {
			echo '<a class="issue-fix" href="' . esc_url( $url ) . '">' . esc_html( $cta ) . ' →</a>';
		}
		echo '</div>';
	}

	/**
	 * Show a friendly getting-started prompt when nothing is enabled yet — the
	 * zero-config first-run state for a brand-new install.
	 *
	 * @param array<string,mixed> $pulse
	 */
	private function render_getting_started( array $pulse ): void {
		$enabled_count = (int) ( $pulse['enabled_modules'] ?? 0 );
		$total_modules = (int) ( $pulse['total_modules']   ?? 0 );

		// Only show this strip when there's genuinely nothing running.
		if ( $enabled_count > 0 || 0 === $total_modules ) {
			return;
		}
		?>
		<?php
		Ui::notice(
			'<strong>' . esc_html__( 'Welcome to Platform Helm.', 'platform-helm' ) . '</strong> '
			. esc_html__( 'No modules are enabled yet — turn one on to get started. Each is self-contained and adds zero overhead while off.', 'platform-helm' )
			. ' <a href="' . esc_url( admin_url( 'admin.php?page=' . SettingsPage::PAGE_SLUG ) ) . '">' . esc_html__( 'Choose modules →', 'platform-helm' ) . '</a>',
			'info'
		);
	}

	/**
	 * A lightweight "get to value" checklist shown until each step is done.
	 * Steps are derived from real state (modules enabled, scan run, links
	 * built) so it reflects genuine progress, not a static list.
	 *
	 * @param array<string,mixed> $pulse
	 */
	private function render_setup_checklist( array $pulse ): void {
		$enabled_count = (int) ( $pulse['enabled_modules'] ?? 0 );
		$has_scan      = null !== ( $pulse['orphan_count'] ?? null );
		$total_links   = (int) ( $pulse['total_links'] ?? 0 );

		// All three done → nothing to show; keeps the dashboard clean once the
		// user is up and running.
		$steps = [
			[
				'done'  => $enabled_count > 0,
				'label' => __( 'Enable at least one module', 'platform-helm' ),
				'href'  => admin_url( 'admin.php?page=' . SettingsPage::PAGE_SLUG ),
				'cta'   => __( 'Choose modules', 'platform-helm' ),
			],
			[
				'done'  => $has_scan,
				'label' => __( 'Run your first link-health scan', 'platform-helm' ),
				'href'  => admin_url( 'admin.php?page=platform-helm-link-health' ),
				'cta'   => __( 'Open Link Health', 'platform-helm' ),
			],
			[
				'done'  => $total_links > 0,
				'label' => __( 'Build or write your first internal link', 'platform-helm' ),
				'href'  => admin_url( 'admin.php?page=platform-helm-link-architect' ),
				'cta'   => __( 'Open Link Architect', 'platform-helm' ),
			],
		];

		$remaining = array_filter( $steps, static fn( $s ) => ! $s['done'] );
		if ( empty( $remaining ) ) {
			return; // Fully set up — hide the checklist.
		}
		$done_count = count( $steps ) - count( $remaining );
		Ui::card_open(
			__( 'Getting started', 'platform-helm' ),
			'<span class="text-faint">' . esc_html( sprintf( /* translators: 1: completed, 2: total */ __( '%1$d of %2$d done', 'platform-helm' ), $done_count, count( $steps ) ) ) . '</span>',
			'ti-checklist'
		);
		echo '<ul class="ph-checklist">';
		foreach ( $steps as $s ) {
			$icon = $s['done'] ? 'ti-check' : 'ti-circle';
			echo '<li class="ph-check ' . ( $s['done'] ? 'is-done' : '' ) . '">';
			echo '<span class="ph-check-mark" aria-hidden="true"><i class="ti ' . esc_attr( $icon ) . '"></i></span>';
			echo '<span class="ph-check-label">' . esc_html( (string) $s['label'] ) . '</span>';
			if ( ! $s['done'] ) {
				echo '<a class="btn btn-sm btn-ghost" href="' . esc_url( (string) $s['href'] ) . '">' . esc_html( (string) $s['cta'] ) . ' <i class="ti ti-arrow-right"></i></a>';
			}
			echo '</li>';
		}
		echo '</ul>';
		Ui::card_close();
	}

	/**
	 * Recent-activity feed, fed by the Activity Log module via the dashboard
	 * metrics filter. Hidden entirely when the module is off / no events.
	 *
	 * @param array<string,mixed> $pulse
	 */
	private function render_activity_feed( array $pulse ): void {
		$feed = $pulse['activity_feed'] ?? null;
		if ( ! is_array( $feed ) || empty( $feed ) ) {
			return;
		}
		Ui::card_open(
			__( 'Recent activity', 'platform-helm' ),
			'<a class="card-action" href="' . esc_url( admin_url( 'admin.php?page=platform-helm-activity-log' ) ) . '">' . esc_html__( 'View all', 'platform-helm' ) . ' →</a>',
			'ti-timeline'
		);
		echo '<ul class="ph-feed">';
		foreach ( $feed as $item ) {
			$w = (string) ( $item['when'] ?? '' );
			echo '<li class="ph-feed-item">';
			echo '<span class="ph-feed-desc">' . esc_html( (string) ( $item['description'] ?? '' ) ) . '</span>';
			echo '<span class="ph-feed-when">' . esc_html( '' !== $w ? sprintf( /* translators: %s: time ago */ __( '%s ago', 'platform-helm' ), $w ) : '' ) . '</span>';
			echo '</li>';
		}
		echo '</ul>';
		Ui::card_close();
	}

	/* ===================================================================== *
	 * DATA ENGINE
	 * ===================================================================== */

	/**
	 * @return array<string,mixed>
	 */
	private function get_pulse_data(): array {
		$cached = get_transient( self::TRANSIENT_KEY );
		if ( is_array( $cached ) ) {
			return $cached;
		}
		$fresh = $this->compute_pulse_data();
		set_transient( self::TRANSIENT_KEY, $fresh, self::TRANSIENT_TTL );
		return $fresh;
	}

	/**
	 * @return array<string,mixed>
	 */
	private function compute_pulse_data(): array {
		/**
		 * Filter dashboard metrics. Modules (e.g. Link Health) hook in to
		 * supply real numbers; absent modules leave the defaults, which the
		 * UI renders as a neutral dash.
		 *
		 * @param array<string,mixed> $metrics
		 */
		$metrics = apply_filters( 'platform_helm_dashboard_metrics', [
			'orphan_count' => null,
			'total_links'  => null,
			'broken_links' => null,
		] );

		return [
			'generated_at'    => time(),
			'total_modules'   => count( $this->registry->all() ),
			'enabled_modules' => $this->count_enabled_modules(),
			'orphan_count'    => $metrics['orphan_count'] ?? null,
			'total_links'     => $metrics['total_links'] ?? null,
			'broken_links'    => $metrics['broken_links'] ?? null,
			'activity_feed'   => $metrics['activity_feed'] ?? null,
		];
	}

	private function count_enabled_modules(): int {
		$count = 0;
		foreach ( $this->registry->all() as $module ) {
			if ( $this->settings->is_module_enabled( $module->id(), $module->default_enabled() ) ) {
				$count++;
			}
		}
		return $count;
	}

	
}
