<?php

declare(strict_types=1);

namespace Australcode\Rewards\Admin;

use Australcode\Rewards\Domain\Settings\Settings;
use Australcode\Rewards\Domain\Settings\SettingsRepository;
use Australcode\Rewards\Domain\Wallet\Balance;
use Australcode\Rewards\Domain\Wallet\BalanceDirectory;
use Australcode\Rewards\Domain\Wallet\ExpiryGateway;
use Australcode\Rewards\Domain\Wallet\LedgerAnalytics;
use Australcode\Rewards\Service\Licensing\LicenseGate;

defined( 'ABSPATH' ) || exit;

/**
 * Registers the top-level "Australcode Rewards" admin menu and its sub-pages, and
 * renders the data-first Dashboard it owns. Customers, Tiers, Campaigns, Settings
 * and Upgrade are delegated to their own page classes. Licensing (Account /
 * Pricing) is provided by the native Freemius pages, nested under this menu via
 * the SDK's `menu.slug`.
 *
 * The Dashboard leads with a hero metric (points in circulation) + 30-day trend
 * delta + an inline server-rendered sparkline, then demoted secondary KPIs and
 * an actionable band (top earners, top movers, expiring soon). It absorbed the
 * former standalone "Reports" page, which only mirrored a subset of these.
 */
final class AdminMenu {

	use RendersPageHeader;

	private const CAPABILITY  = 'manage_woocommerce';
	public const ROOT_SLUG    = 'acrwd';
	private const DASH_TOP    = 5;
	private const TREND_DAYS  = 30;
	private const MOVERS_DAYS = 7;

	public function __construct(
		private readonly SettingsRepository $settings,
		private readonly SettingsPage $settings_page,
		private readonly CustomersPage $customers_page,
		private readonly ImportPage $import_page,
		private readonly HelpPage $help_page,
		private readonly BalanceDirectory $balances,
		private readonly LicenseGate $gate,
		private readonly LedgerAnalytics $analytics,
		private readonly ExpiryGateway $expiry,
	) {
	}

	public function register(): void {
		add_action( 'admin_menu', array( $this, 'register_menu' ) );
		// admin_enqueue_scripts (not admin_head): the kit menu-icon partial now
		// enqueues its CSS via wp_add_inline_style (WP.org compliance, kit v1.1.26),
		// which must run in the styles phase.
		add_action( 'admin_enqueue_scripts', array( $this, 'print_menu_icon_style' ) );
	}

	public function register_menu(): void {
		add_menu_page(
			__( 'Australcode Points and Rewards', 'australcode-points-rewards' ),
			__( 'Australcode Points and Rewards', 'australcode-points-rewards' ),
			self::CAPABILITY,
			self::ROOT_SLUG,
			array( $this, 'render_dashboard' ),
			$this->menu_icon(),
			// WP usa la posición como clave string del array $menu, así que una
			// string-decimal como '58.4' ordena los plugins de la suite Australcode
			// sin colisionar; el stub la tipa int|float, de ahí el ignore puntual.
			'58.4' // @phpstan-ignore argument.type
		);

		// Core sub-pages only. Tiers (position 2) and Campaigns (position 3) are Pro:
		// they self-register their own submenu (TiersPage/CampaignsPage::register,
		// admin_menu priority 11), so this core menu never references those
		// @fs_premium_only classes — that is what lets the free WP.org build drop them
		// without fataling. Import and Help are core (both builds), so they live here.
		// Explicit positions keep the intended order: Dashboard, Customers, [Tiers],
		// [Campaigns], Settings, Import, Help, then Upgrade (UpgradePage, prio 12).
		$subpages = array(
			self::ROOT_SLUG   => __( 'Dashboard', 'australcode-points-rewards' ),
			'acrwd-customers' => __( 'Customers', 'australcode-points-rewards' ),
			'acrwd-settings'  => __( 'Settings', 'australcode-points-rewards' ),
			'acrwd-import'    => __( 'Import', 'australcode-points-rewards' ),
			'acrwd-help'      => __( 'Help', 'australcode-points-rewards' ),
		);

		$callbacks = array(
			self::ROOT_SLUG   => array( $this, 'render_dashboard' ),
			'acrwd-customers' => array( $this->customers_page, 'render' ),
			'acrwd-settings'  => array( $this->settings_page, 'render' ),
			'acrwd-import'    => array( $this->import_page, 'render' ),
			'acrwd-help'      => array( $this->help_page, 'render' ),
		);

		$positions = array(
			self::ROOT_SLUG   => 0,
			'acrwd-customers' => 1,
			'acrwd-settings'  => 4,
			'acrwd-import'    => 5,
			'acrwd-help'      => 6,
		);

		foreach ( $subpages as $slug => $title ) {
			add_submenu_page(
				self::ROOT_SLUG,
				/* translators: %s: sub-page title. */
				sprintf( __( '%s — Australcode Rewards', 'australcode-points-rewards' ), $title ),
				$title,
				self::CAPABILITY,
				$slug,
				$callbacks[ $slug ],
				$positions[ $slug ]
			);
		}
	}

	/**
	 * Icono del menú: `'none'` para que WP emita el slot `.wp-menu-image` vacío que
	 * {@see self::print_menu_icon_style()} pinta con el glyph del producto (estrella)
	 * vía `mask` + `background-color:currentColor`. Así el icono **recolorea** como los
	 * dashicons nativos (gris inactivo → blanco en hover/activo, adapta a esquemas), a
	 * diferencia del data-URI como background-image que WP NO recolorea. Fallback al
	 * dashicon si el asset falta. Espejo del back-in-stock (canon de familia 8.121).
	 */
	private function menu_icon(): string {
		$svg = ACRWD_PATH . 'assets/img/glyph-menu.svg';
		return is_readable( $svg ) ? 'none' : 'dashicons-star-filled';
	}

	/**
	 * CSS del icono del menú (estrella): delega al parcial canónico del Admin Kit
	 * (`assets/acds/menu-icon.php`, B-136 / kit ≥v1.1.19) que pinta el glyph con
	 * `mask` + `currentColor` → recolorea en todos los estados. Antes vivía inline
	 * acá; centralizado en el kit para no reinventarlo.
	 */
	public function print_menu_icon_style(): void {
		$acds_menu_slug  = self::ROOT_SLUG;
		$acds_glyph_url  = ACRWD_URL . 'assets/img/glyph-menu.svg';
		$acds_glyph_path = ACRWD_PATH . 'assets/img/glyph-menu.svg';
		require ACRWD_PATH . 'assets/acds/menu-icon.php';
	}

	public function render_dashboard(): void {
		$totals      = $this->balances->totals();
		$settings    = $this->settings->get();
		$plural      = $settings->points_label_plural;
		$is_pro      = $this->gate->is_pro_active();
		$tier        = $this->gate->tier();
		$license     = $is_pro && null !== $tier ? $tier->label() : __( 'Free', 'australcode-points-rewards' );
		$outstanding = (int) $totals['outstanding'];

		$now_ts = time();
		$series = $this->analytics->daily_series(
			gmdate( 'Y-m-d H:i:s', $now_ts - self::TREND_DAYS * DAY_IN_SECONDS ),
			gmdate( 'Y-m-d H:i:s', $now_ts )
		);

		echo '<div class="wrap acds-root acrwd-page"><div class="acds-wrap">';
		$this->render_page_header(
			__( 'Dashboard', 'australcode-points-rewards' ),
			__( 'How your rewards program is doing — and what needs your attention today.', 'australcode-points-rewards' )
		);

		$this->render_quickstart( $settings, (int) $totals['members'], $is_pro );
		$this->render_hero( $plural, $outstanding, $series, $now_ts );

		echo '<div class="acrwd-kpi-grid acrwd-kpi-grid--secondary">';
		$this->kpi( __( 'Members with points', 'australcode-points-rewards' ), number_format_i18n( $totals['members'] ) );
		$this->kpi( __( 'Lifetime earned', 'australcode-points-rewards' ), number_format_i18n( $totals['lifetime_earned'] ), $this->lifetime_tooltip() );
		$this->kpi( __( 'Lifetime redeemed', 'australcode-points-rewards' ), number_format_i18n( $totals['lifetime_redeemed'] ), $this->lifetime_tooltip() );
		$this->kpi( __( 'License', 'australcode-points-rewards' ), $license );
		echo '</div>';

		$this->render_top_earners();

		echo '<div class="acrwd-dash-cols">';
		$this->render_top_movers( $now_ts );
		if ( $is_pro && $settings->points_expiration_months > 0 ) {
			$this->render_expiring( $now_ts );
		}
		echo '</div>';

		echo '</div></div>';
	}

	/**
	 * Quick-start checklist (A-102). Guides a new store through the few steps to
	 * get the program running, and disappears once the essentials are in place
	 * (earning rate set + customers collecting points) so established stores see
	 * a clean dashboard.
	 */
	private function render_quickstart( Settings $settings, int $members, bool $is_pro ): void {
		$rate_set    = $settings->points_per_currency_unit > 0;
		$has_members = $members > 0;

		if ( $rate_set && $has_members ) {
			return;
		}

		echo '<div class="acds-surface"><div class="acds-surface__head"><h2 class="acds-surface__title">'
			. esc_html__( 'Quick start', 'australcode-points-rewards' ) . '</h2></div><div class="acds-surface__body">';
		echo '<p class="acds-muted">' . esc_html__( 'A few steps to get your rewards program running. This panel hides itself once the essentials are set.', 'australcode-points-rewards' ) . '</p>';

		$this->checklist_item(
			$rate_set,
			__( 'Set your earning rate', 'australcode-points-rewards' ),
			admin_url( 'admin.php?page=acrwd-settings' ),
			__( 'Configure earning', 'australcode-points-rewards' )
		);
		$this->checklist_item(
			$has_members,
			__( 'Customers are collecting points', 'australcode-points-rewards' ),
			'',
			''
		);
		$this->checklist_item(
			$is_pro,
			__( 'Activate Pro to unlock bonuses, tiers, referrals and campaigns (optional)', 'australcode-points-rewards' ),
			UpgradeCta::can_manage_license() ? $this->gate->upgrade_url() : '',
			UpgradeCta::can_manage_license() ? __( 'Manage license', 'australcode-points-rewards' ) : ''
		);

		echo '</div></div>';
	}

	private function checklist_item( bool $done, string $label, string $url, string $cta ): void {
		$icon = $done ? 'dashicons-yes-alt' : 'dashicons-marker';

		echo '<p class="acrwd-checklist-item"><span class="dashicons ' . esc_attr( $icon ) . '"></span> ';
		echo '<span>' . esc_html( $label ) . '</span>';
		if ( ! $done && '' !== $url && '' !== $cta ) {
			echo ' <a class="acds-btn acds-btn--secondary acds-btn--sm" href="' . esc_url( $url ) . '">' . esc_html( $cta ) . '</a>';
		}
		echo '</p>';
	}

	/**
	 * Hero band: the dominant "points in circulation" metric with its 30-day trend
	 * delta, and an inline sparkline of the outstanding-balance trajectory. One
	 * headline answers "is the program growing?" — not five flat counters (L-026).
	 *
	 * @param array<string,int> $series Net points per 'Y-m-d', gaps allowed.
	 */
	private function render_hero( string $plural, int $outstanding, array $series, int $now_ts ): void {
		$net  = array_sum( $series );
		$base = $outstanding - $net;

		echo '<div class="acrwd-hero">';
		echo '<div class="acrwd-hero__main">';
		/* translators: %s: points label (plural). */
		echo '<div class="acrwd-hero__label">' . esc_html( sprintf( __( 'Outstanding %s', 'australcode-points-rewards' ), $plural ) ) . '</div>';
		echo '<div class="acrwd-hero__row"><span class="acrwd-hero__value">' . esc_html( number_format_i18n( $outstanding ) ) . '</span>';
		echo $this->delta_badge( (int) $net, (int) $base ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- returns pre-escaped markup.
		echo '</div>';
		echo '<div class="acrwd-hero__sub">' . esc_html__( 'vs. the previous 30 days', 'australcode-points-rewards' ) . '</div>';
		echo '</div>';
		echo '<div class="acrwd-hero__spark">';
		echo '<div class="acrwd-hero__spark-label">' . esc_html__( 'Trend · last 30 days', 'australcode-points-rewards' ) . '</div>';
		$this->sparkline( $outstanding, $series, $now_ts );
		echo '</div>';
		echo '</div>';
	}

	/**
	 * Trend badge: outstanding growth over the window as a percentage of the
	 * starting balance, or an absolute signed figure when there is no prior base.
	 * Returns pre-escaped markup (the caller echoes it raw).
	 */
	private function delta_badge( int $net, int $base ): string {
		if ( 0 === $net ) {
			return '';
		}

		$up   = $net > 0;
		$dir  = $up ? 'up' : 'down';
		$icon = $up ? 'dashicons-arrow-up-alt' : 'dashicons-arrow-down-alt';

		if ( $base > 0 ) {
			$text = number_format_i18n( round( abs( $net ) / $base * 100, 1 ), 1 ) . '%';
		} else {
			$text = ( $up ? '+' : '−' ) . number_format_i18n( abs( $net ) );
		}

		return '<span class="acrwd-delta acrwd-delta--' . esc_attr( $dir ) . '">'
			. '<span class="dashicons ' . esc_attr( $icon ) . '" aria-hidden="true"></span>'
			. esc_html( $text ) . '</span>';
	}

	/**
	 * Inline SVG sparkline of the outstanding-balance trajectory across the window.
	 * Pure server-side (no JS, no library): reconstructs the end-of-day balance by
	 * walking backwards from today's outstanding total over the per-day net series.
	 *
	 * @param array<string,int> $series Net points per 'Y-m-d', gaps allowed.
	 */
	private function sparkline( int $outstanding, array $series, int $now_ts ): void {
		$days    = self::TREND_DAYS;
		$today   = (int) floor( $now_ts / DAY_IN_SECONDS );
		$values  = array();
		$running = $outstanding;
		for ( $i = 0; $i <= $days; $i++ ) {
			$values[ $days - $i ] = $running;
			$running             -= (int) ( $series[ gmdate( 'Y-m-d', ( $today - $i ) * DAY_IN_SECONDS ) ] ?? 0 );
		}
		ksort( $values );
		$values = array_values( $values );

		$width  = 300;
		$height = 64;
		$min    = (int) min( $values );
		$max    = (int) max( $values );
		$range  = max( 1, $max - $min );
		$count  = count( $values );

		$points = array();
		foreach ( $values as $idx => $value ) {
			$x        = $count > 1 ? round( $idx / ( $count - 1 ) * $width, 1 ) : 0.0;
			$y        = round( $height - 3 - ( ( $value - $min ) / $range ) * ( $height - 6 ), 1 );
			$points[] = $x . ',' . $y;
		}
		$line = implode( ' ', $points );
		$area = '0,' . $height . ' ' . $line . ' ' . $width . ',' . $height;

		printf(
			'<svg class="acrwd-spark" viewBox="0 0 %1$d %2$d" preserveAspectRatio="none" role="img" aria-label="%3$s">'
				. '<polyline class="acrwd-spark__area" points="%4$s" />'
				. '<polyline class="acrwd-spark__line" points="%5$s" vector-effect="non-scaling-stroke" />'
				. '</svg>',
			(int) $width,
			(int) $height,
			esc_attr__( 'Outstanding points trend over the last 30 days', 'australcode-points-rewards' ),
			esc_attr( $area ),
			esc_attr( $line )
		);
	}

	/**
	 * Top earners — the customers holding the most points. Absorbed from the
	 * retired Reports page (it was a partial mirror of the Dashboard).
	 */
	private function render_top_earners(): void {
		$top = $this->balances->paginate( self::DASH_TOP, 0 );

		echo '<div class="acds-surface"><div class="acds-surface__head"><h2 class="acds-surface__title">'
			. esc_html__( 'Top earners', 'australcode-points-rewards' ) . '</h2>'
			. '<span class="acrwd-surface-meta">' . esc_html__( 'customers with the highest balance', 'australcode-points-rewards' ) . '</span></div>';

		if ( array() === $top ) {
			echo '<div class="acds-surface__body"><div class="acds-empty"><p class="acds-empty__title">'
				. esc_html__( 'No customers with points yet', 'australcode-points-rewards' ) . '</p>';
			echo '<p class="acds-empty__body">' . esc_html__( 'Top earners appear here once customers start collecting points.', 'australcode-points-rewards' ) . '</p></div></div>';
		} else {
			echo '<table class="wp-list-table widefat fixed striped acrwd-table-cards"><thead><tr>';
			echo '<th>' . esc_html__( 'Customer', 'australcode-points-rewards' ) . '</th>';
			echo '<th>' . esc_html__( 'Balance', 'australcode-points-rewards' ) . '</th>';
			echo '<th>' . esc_html__( 'Lifetime earned', 'australcode-points-rewards' ) . '</th>';
			echo '</tr></thead><tbody>';
			foreach ( $top as $balance ) {
				$this->earner_row( $balance );
			}
			echo '</tbody></table>';
		}
		echo '</div>';
	}

	private function earner_row( Balance $balance ): void {
		echo '<tr>';
		echo '<td data-label="' . esc_attr__( 'Customer', 'australcode-points-rewards' ) . '">' . esc_html( $this->display_name( $balance->user_id ) ) . '</td>';
		echo '<td data-label="' . esc_attr__( 'Balance', 'australcode-points-rewards' ) . '">' . esc_html( number_format_i18n( $balance->points_balance ) ) . '</td>';
		echo '<td data-label="' . esc_attr__( 'Lifetime earned', 'australcode-points-rewards' ) . '">' . esc_html( number_format_i18n( $balance->lifetime_earned ) ) . '</td>';
		echo '</tr>';
	}

	/**
	 * Top movers — the most active customers over the last 7 days, with their net
	 * change. Answers "who is engaging right now?".
	 */
	private function render_top_movers( int $now_ts ): void {
		$movers = $this->analytics->top_movers(
			gmdate( 'Y-m-d H:i:s', $now_ts - self::MOVERS_DAYS * DAY_IN_SECONDS ),
			gmdate( 'Y-m-d H:i:s', $now_ts ),
			self::DASH_TOP
		);

		echo '<div class="acds-surface acrwd-dash-panel"><div class="acds-surface__head"><h2 class="acds-surface__title">'
			. esc_html__( 'Top movers', 'australcode-points-rewards' ) . '</h2>'
			. '<span class="acrwd-surface-meta">' . esc_html__( 'last 7 days', 'australcode-points-rewards' ) . '</span></div><div class="acds-surface__body">';

		if ( array() === $movers ) {
			echo '<p class="acds-muted">' . esc_html__( 'No points activity in the last 7 days.', 'australcode-points-rewards' ) . '</p>';
		} else {
			echo '<ul class="acrwd-feed">';
			foreach ( $movers as $mover ) {
				$net  = (int) $mover['net'];
				$sign = $net >= 0 ? '+' : '−';
				$cls  = $net >= 0 ? 'acrwd-feed__num--up' : 'acrwd-feed__num--down';
				echo '<li class="acrwd-feed__row"><span>' . esc_html( $this->display_name( (int) $mover['user_id'] ) ) . '</span>';
				echo '<span class="acrwd-feed__num ' . esc_attr( $cls ) . '">' . esc_html( $sign . number_format_i18n( abs( $net ) ) ) . '</span></li>';
			}
			echo '</ul>';
		}
		echo '</div></div>';
	}

	/**
	 * Expiring soon (Pro) — Earn batches due to expire in the next 30 days, so the
	 * merchant can nudge customers before they lose points.
	 */
	private function render_expiring( int $now_ts ): void {
		$upcoming = $this->expiry->upcoming(
			gmdate( 'Y-m-d H:i:s', $now_ts ),
			gmdate( 'Y-m-d H:i:s', $now_ts + 30 * DAY_IN_SECONDS ),
			self::DASH_TOP
		);
		$fmt      = (string) get_option( 'date_format' );

		echo '<div class="acds-surface acrwd-dash-panel"><div class="acds-surface__head"><h2 class="acds-surface__title">'
			. esc_html__( 'Expiring soon', 'australcode-points-rewards' ) . ' <span class="acds-badge acds-badge--pro">' . esc_html__( 'Pro', 'australcode-points-rewards' ) . '</span></h2>'
			. '<span class="acrwd-surface-meta">' . esc_html__( 'next 30 days', 'australcode-points-rewards' ) . '</span></div><div class="acds-surface__body">';

		if ( array() === $upcoming ) {
			echo '<p class="acds-muted">' . esc_html__( 'No points expiring in the next 30 days.', 'australcode-points-rewards' ) . '</p>';
		} else {
			echo '<ul class="acrwd-feed">';
			foreach ( $upcoming as $tx ) {
				$when = null !== $tx->expires_at ? (string) mysql2date( $fmt, $tx->expires_at ) : '';
				echo '<li class="acrwd-feed__row"><span>' . esc_html( $this->display_name( $tx->user_id ) ) . '</span>';
				echo '<span class="acrwd-feed__meta">' . esc_html( number_format_i18n( $tx->points_delta ) . ' · ' . $when ) . '</span></li>';
			}
			echo '</ul>';
		}
		echo '</div></div>';
	}

	private function display_name( int $user_id ): string {
		$user = get_userdata( $user_id );
		return ( false !== $user && '' !== $user->display_name )
			? $user->display_name
			/* translators: %d: WordPress user id. */
			: sprintf( __( 'User #%d', 'australcode-points-rewards' ), $user_id );
	}

	private function kpi( string $label, string $value, ?string $tooltip = null ): void {
		echo '<div class="acds-kpi"><span class="acds-kpi__value">' . esc_html( $value ) . '</span>';
		echo '<span class="acds-kpi__label">' . esc_html( $label );
		if ( null !== $tooltip ) {
			printf(
				' <span class="acrwd-kpi-info dashicons dashicons-info-outline" tabindex="0" role="img" title="%1$s" aria-label="%1$s"></span>',
				esc_attr( $tooltip )
			);
		}
		echo '</span></div>';
	}

	/**
	 * Explains why the lifetime KPIs do not reconcile at a glance: they are gross
	 * running totals (never reduced by refunds) and seed from any program migrated
	 * in (e.g. a previous loyalty plugin), so they exceed the live balance.
	 */
	private function lifetime_tooltip(): string {
		return __( 'Gross running total — never reduced by refunds, and includes points migrated from a previous loyalty program. It will exceed the live balance.', 'australcode-points-rewards' );
	}
}
