<?php
/**
 * Achievement system for gamification dashboard.
 *
 * @package AgenticWP
 */

namespace Agentic_WP;

defined( 'ABSPATH' ) || exit;

/**
 * Manages achievement definitions, tiers, and unlock logic.
 *
 * @since 1.0.0
 */
class Achievements {

	/**
	 * Achievement tiers.
	 */
	const TIER_BRONZE   = 'bronze';
	const TIER_SILVER   = 'silver';
	const TIER_GOLD     = 'gold';
	const TIER_PLATINUM = 'platinum';

	/**
	 * Achievement categories.
	 */
	const CATEGORY_CONTENT  = 'content';
	const CATEGORY_SEO      = 'seo';
	const CATEGORY_STREAK   = 'streak';
	const CATEGORY_TOOLS    = 'tools';
	const CATEGORY_DELEGATE = 'delegate';

	/**
	 * Singleton instance.
	 *
	 * @var self|null
	 */
	private static ?self $instance = null;

	/**
	 * Achievement definitions.
	 *
	 * @var array
	 */
	private array $achievements = array();

	/**
	 * Gets the singleton instance.
	 *
	 * @return self
	 */
	public static function get_instance(): self {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Private constructor to enforce singleton.
	 */
	private function __construct() {
		$this->register_hooks();
	}

	/**
	 * Registers WordPress hooks.
	 */
	private function register_hooks(): void {
		add_action( 'agentic_wp_after_tool_execution', array( $this, 'check_achievements' ), 20, 3 );
	}

	/**
	 * Defines all available achievements.
	 */
	private function define_achievements(): void {
		$this->achievements = array(
			// Content Creator achievements.
			'first_steps'        => array(
				'name'        => __( 'First Steps', 'agenticwp' ),
				'description' => __( 'Create your first draft', 'agenticwp' ),
				'tier'        => self::TIER_BRONZE,
				'category'    => self::CATEGORY_CONTENT,
				'icon'        => "\u{1F680}",
				'requirement' => array(
					'stat'      => 'posts_created',
					'threshold' => 1,
				),
			),
			'content_creator'    => array(
				'name'        => __( 'Content Creator', 'agenticwp' ),
				'description' => __( 'Create 10 drafts', 'agenticwp' ),
				'tier'        => self::TIER_SILVER,
				'category'    => self::CATEGORY_CONTENT,
				'icon'        => "\u{270D}\u{FE0F}",
				'requirement' => array(
					'stat'      => 'posts_created',
					'threshold' => 10,
				),
			),
			'prolific_publisher' => array(
				'name'        => __( 'Prolific Publisher', 'agenticwp' ),
				'description' => __( 'Create 50 drafts', 'agenticwp' ),
				'tier'        => self::TIER_GOLD,
				'category'    => self::CATEGORY_CONTENT,
				'icon'        => "\u{1F4DA}",
				'requirement' => array(
					'stat'      => 'posts_created',
					'threshold' => 50,
				),
			),
			'publishing_legend'  => array(
				'name'        => __( 'Publishing Legend', 'agenticwp' ),
				'description' => __( 'Create 100 drafts', 'agenticwp' ),
				'tier'        => self::TIER_PLATINUM,
				'category'    => self::CATEGORY_CONTENT,
				'icon'        => "\u{1F3C6}",
				'requirement' => array(
					'stat'      => 'posts_created',
					'threshold' => 100,
				),
			),

			// SEO Master achievements.
			'seo_starter'        => array(
				'name'        => __( 'SEO Starter', 'agenticwp' ),
				'description' => __( 'Save 10 meta descriptions', 'agenticwp' ),
				'tier'        => self::TIER_BRONZE,
				'category'    => self::CATEGORY_SEO,
				'icon'        => "\u{1F50D}",
				'requirement' => array(
					'stat'      => 'meta_descriptions',
					'threshold' => 10,
				),
			),
			'seo_pro'            => array(
				'name'        => __( 'SEO Pro', 'agenticwp' ),
				'description' => __( 'Save 50 meta descriptions', 'agenticwp' ),
				'tier'        => self::TIER_SILVER,
				'category'    => self::CATEGORY_SEO,
				'icon'        => "\u{1F4CA}",
				'requirement' => array(
					'stat'      => 'meta_descriptions',
					'threshold' => 50,
				),
			),
			'seo_master'         => array(
				'name'        => __( 'SEO Master', 'agenticwp' ),
				'description' => __( 'Save 100 meta descriptions', 'agenticwp' ),
				'tier'        => self::TIER_GOLD,
				'category'    => self::CATEGORY_SEO,
				'icon'        => "\u{1F396}\u{FE0F}",
				'requirement' => array(
					'stat'      => 'meta_descriptions',
					'threshold' => 100,
				),
			),

			// Streak Champion achievements.
			'week_warrior'       => array(
				'name'        => __( 'Week Warrior', 'agenticwp' ),
				'description' => __( '7-day streak', 'agenticwp' ),
				'tier'        => self::TIER_BRONZE,
				'category'    => self::CATEGORY_STREAK,
				'icon'        => "\u{1F525}",
				'requirement' => array(
					'stat'      => 'current_streak',
					'threshold' => 7,
					'source'    => 'streaks',
				),
			),
			'month_master'       => array(
				'name'        => __( 'Month Master', 'agenticwp' ),
				'description' => __( '30-day streak', 'agenticwp' ),
				'tier'        => self::TIER_SILVER,
				'category'    => self::CATEGORY_STREAK,
				'icon'        => "\u{26A1}",
				'requirement' => array(
					'stat'      => 'current_streak',
					'threshold' => 30,
					'source'    => 'streaks',
				),
			),
			'century_champion'   => array(
				'name'        => __( 'Century Champion', 'agenticwp' ),
				'description' => __( '100-day streak', 'agenticwp' ),
				'tier'        => self::TIER_GOLD,
				'category'    => self::CATEGORY_STREAK,
				'icon'        => "\u{1F4AB}",
				'requirement' => array(
					'stat'      => 'current_streak',
					'threshold' => 100,
					'source'    => 'streaks',
				),
			),

			// Tool usage achievements.
			'tool_apprentice'    => array(
				'name'        => __( 'Task Apprentice', 'agenticwp' ),
				'description' => __( '100 tasks completed', 'agenticwp' ),
				'tier'        => self::TIER_BRONZE,
				'category'    => self::CATEGORY_TOOLS,
				'icon'        => "\u{1F527}",
				'requirement' => array(
					'stat'      => 'total_tool_calls',
					'threshold' => 100,
				),
			),
			'tool_expert'        => array(
				'name'        => __( 'Task Expert', 'agenticwp' ),
				'description' => __( '500 tasks completed', 'agenticwp' ),
				'tier'        => self::TIER_SILVER,
				'category'    => self::CATEGORY_TOOLS,
				'icon'        => "\u{2699}\u{FE0F}",
				'requirement' => array(
					'stat'      => 'total_tool_calls',
					'threshold' => 500,
				),
			),
			'tool_master'        => array(
				'name'        => __( 'Task Master', 'agenticwp' ),
				'description' => __( '1,000 tasks completed', 'agenticwp' ),
				'tier'        => self::TIER_GOLD,
				'category'    => self::CATEGORY_TOOLS,
				'icon'        => "\u{1F6E0}\u{FE0F}",
				'requirement' => array(
					'stat'      => 'total_tool_calls',
					'threshold' => 1000,
				),
			),

			// Delegation achievements.
			'automation_pro'     => array(
				'name'        => __( 'Automation Pro', 'agenticwp' ),
				'description' => __( 'Delegate 10 agent jobs', 'agenticwp' ),
				'tier'        => self::TIER_BRONZE,
				'category'    => self::CATEGORY_DELEGATE,
				'icon'        => "\u{1F916}",
				'requirement' => array(
					'stat'      => 'agent_jobs',
					'threshold' => 10,
					'source'    => 'calculated',
				),
			),
			'delegation_master'  => array(
				'name'        => __( 'Delegation Master', 'agenticwp' ),
				'description' => __( 'Delegate 50 agent jobs', 'agenticwp' ),
				'tier'        => self::TIER_SILVER,
				'category'    => self::CATEGORY_DELEGATE,
				'icon'        => "\u{1F39B}\u{FE0F}",
				'requirement' => array(
					'stat'      => 'agent_jobs',
					'threshold' => 50,
					'source'    => 'calculated',
				),
			),
		);
	}

	/**
	 * Gets achievements with lazy initialization.
	 *
	 * Defers loading achievement definitions until they are needed,
	 * ensuring the textdomain is loaded before translation functions are called.
	 *
	 * @return array Achievement definitions.
	 */
	private function get_achievements(): array {
		if ( empty( $this->achievements ) ) {
			$this->define_achievements();
		}
		return $this->achievements;
	}

	/**
	 * Gets all achievement definitions.
	 *
	 * @return array Achievement definitions.
	 */
	public function get_all_achievements(): array {
		return $this->get_achievements();
	}

	/**
	 * Gets a specific achievement definition.
	 *
	 * @param string $id Achievement ID.
	 * @return array|null Achievement definition or null if not found.
	 */
	public function get_achievement( string $id ): ?array {
		return $this->get_achievements()[ $id ] ?? null;
	}

	/**
	 * Gets achievements by category.
	 *
	 * @param string $category Category constant.
	 * @return array Achievements in the category.
	 */
	public function get_achievements_by_category( string $category ): array {
		return array_filter(
			$this->get_achievements(),
			fn( $achievement ) => $achievement['category'] === $category
		);
	}

	/**
	 * Gets achievements by tier.
	 *
	 * @param string $tier Tier constant.
	 * @return array Achievements in the tier.
	 */
	public function get_achievements_by_tier( string $tier ): array {
		return array_filter(
			$this->get_achievements(),
			fn( $achievement ) => $achievement['tier'] === $tier
		);
	}

	/**
	 * Checks and unlocks achievements based on current stats.
	 *
	 * Called after each tool execution to check if any new achievements
	 * should be unlocked.
	 *
	 * @param string $name   Tool name that was executed.
	 * @param array  $args   Tool arguments.
	 * @param string $result Tool execution result.
	 * @return array Newly unlocked achievement IDs.
	 */
	public function check_achievements( string $name, array $args, string $result ): array {
		// Skip if tool execution failed.
		if ( str_starts_with( $result, 'ERROR:' ) ) {
			return array();
		}

		$usage_stats = Usage_Stats::get_instance();
		$stats       = $usage_stats->get_stats();
		$unlocked    = array();

		foreach ( $this->get_achievements() as $id => $achievement ) {
			// Skip if already unlocked.
			if ( $usage_stats->is_achievement_unlocked( $id ) ) {
				continue;
			}

			$current_value = $this->get_stat_value( $achievement['requirement'], $stats, $usage_stats );

			if ( $current_value >= $achievement['requirement']['threshold'] ) {
				if ( $usage_stats->unlock_achievement( $id ) ) {
					$unlocked[] = $id;

					/**
					 * Fires when an achievement is unlocked.
					 *
					 * @param string $id          Achievement ID.
					 * @param array  $achievement Achievement definition.
					 */
					do_action( 'agentic_wp_achievement_unlocked', $id, $achievement );
				}
			}
		}

		// Store newly unlocked achievements for dashboard display.
		if ( ! empty( $unlocked ) ) {
			$this->store_new_achievements( $unlocked );
		}

		return $unlocked;
	}

	/**
	 * Stores newly unlocked achievements in a transient for dashboard display.
	 *
	 * @param array $achievement_ids Newly unlocked achievement IDs.
	 * @return void
	 */
	private function store_new_achievements( array $achievement_ids ): void {
		$user_id       = get_current_user_id();
		$transient_key = 'agenticwp_new_achievements_' . $user_id;

		// Get existing queued achievements.
		$existing = get_transient( $transient_key );
		if ( ! is_array( $existing ) ) {
			$existing = array();
		}

		// Merge and dedupe.
		$all = array_unique( array_merge( $existing, $achievement_ids ) );

		// Store for 1 hour (user should see them on next dashboard visit).
		set_transient( $transient_key, $all, HOUR_IN_SECONDS );
	}

	/**
	 * Gets the current value for an achievement requirement.
	 *
	 * @param array       $requirement Achievement requirement config.
	 * @param array       $stats       Current stats data.
	 * @param Usage_Stats $usage_stats Usage stats instance.
	 * @return int Current stat value.
	 */
	private function get_stat_value( array $requirement, array $stats, Usage_Stats $usage_stats ): int {
		$source = $requirement['source'] ?? 'lifetime';
		$stat   = $requirement['stat'];

		switch ( $source ) {
			case 'streaks':
				return (int) ( $stats['streaks'][ $stat ] ?? 0 );

			case 'calculated':
				if ( 'agent_jobs' === $stat ) {
					return $usage_stats->get_total_agent_jobs();
				}
				return 0;

			case 'lifetime':
			default:
				return (int) ( $stats['lifetime'][ $stat ] ?? 0 );
		}
	}

	/**
	 * Gets progress information for an achievement.
	 *
	 * @param string $id Achievement ID.
	 * @return array Progress with 'current', 'target', 'percentage', 'unlocked'.
	 */
	public function get_achievement_progress( string $id ): array {
		$achievement = $this->get_achievement( $id );

		if ( null === $achievement ) {
			return array(
				'current'    => 0,
				'target'     => 0,
				'percentage' => 0,
				'unlocked'   => false,
			);
		}

		$usage_stats = Usage_Stats::get_instance();
		$stats       = $usage_stats->get_stats();
		$current     = $this->get_stat_value( $achievement['requirement'], $stats, $usage_stats );
		$target      = $achievement['requirement']['threshold'];
		$unlocked    = $usage_stats->is_achievement_unlocked( $id );

		return array(
			'current'    => $current,
			'target'     => $target,
			'percentage' => min( 100, (int) ( ( $current / $target ) * 100 ) ),
			'unlocked'   => $unlocked,
		);
	}

	/**
	 * Gets all achievements with their progress.
	 *
	 * @return array Achievements with progress data.
	 */
	public function get_all_achievements_with_progress(): array {
		$result = array();

		foreach ( $this->get_achievements() as $id => $achievement ) {
			$progress            = $this->get_achievement_progress( $id );
			$result[ $id ]       = array_merge( $achievement, $progress );
			$result[ $id ]['id'] = $id;
		}

		return $result;
	}

	/**
	 * Gets next achievable achievements (closest to unlock).
	 *
	 * @param int $limit Maximum number to return.
	 * @return array Achievements sorted by progress percentage.
	 */
	public function get_next_achievements( int $limit = 3 ): array {
		$all = $this->get_all_achievements_with_progress();

		// Filter to only unlocked achievements.
		$locked = array_filter( $all, fn( $a ) => ! $a['unlocked'] );

		// Sort by percentage descending.
		uasort( $locked, fn( $a, $b ) => $b['percentage'] <=> $a['percentage'] );

		return array_slice( $locked, 0, $limit, true );
	}

	/**
	 * Gets recently unlocked achievements.
	 *
	 * @param int $limit Maximum number to return.
	 * @return array Achievements sorted by unlock time.
	 */
	public function get_recent_achievements( int $limit = 5 ): array {
		$usage_stats = Usage_Stats::get_instance();
		$unlocked    = $usage_stats->get_achievements();
		$result      = array();

		foreach ( $unlocked as $id => $timestamp ) {
			if ( null === $timestamp ) {
				continue;
			}

			$achievement = $this->get_achievement( $id );
			if ( null !== $achievement ) {
				$result[ $id ]                = $achievement;
				$result[ $id ]['id']          = $id;
				$result[ $id ]['unlocked_at'] = $timestamp;
			}
		}

		// Sort by unlock time descending.
		uasort( $result, fn( $a, $b ) => ( $b['unlocked_at'] ?? 0 ) <=> ( $a['unlocked_at'] ?? 0 ) );

		return array_slice( $result, 0, $limit, true );
	}

	/**
	 * Gets tier display information.
	 *
	 * @param string $tier Tier constant.
	 * @return array Tier info with 'name', 'color', 'gradient'.
	 */
	public function get_tier_info( string $tier ): array {
		$tiers = array(
			self::TIER_BRONZE   => array(
				'name'     => __( 'Bronze', 'agenticwp' ),
				'color'    => '#CD7F32',
				'gradient' => array( '#E8A87C', '#CD7F32', '#8B5A2B' ),
			),
			self::TIER_SILVER   => array(
				'name'     => __( 'Silver', 'agenticwp' ),
				'color'    => '#C0C0C0',
				'gradient' => array( '#E8E8E8', '#C0C0C0', '#A8A8A8' ),
			),
			self::TIER_GOLD     => array(
				'name'     => __( 'Gold', 'agenticwp' ),
				'color'    => '#FFD700',
				'gradient' => array( '#FFE066', '#FFD700', '#DAA520' ),
			),
			self::TIER_PLATINUM => array(
				'name'     => __( 'Platinum', 'agenticwp' ),
				'color'    => '#E5E4E2',
				'gradient' => array( '#F5F5F5', '#E5E4E2', '#B8B8B8' ),
			),
		);

		return $tiers[ $tier ] ?? $tiers[ self::TIER_BRONZE ];
	}

	/**
	 * Gets inline SVG badge for a tier.
	 *
	 * @param string $tier Tier constant.
	 * @param int    $size Badge size in pixels.
	 * @return string SVG markup.
	 */
	public function get_tier_badge_svg( string $tier, int $size = 48 ): string {
		$tier_info = $this->get_tier_info( $tier );
		$color     = $tier_info['color'];
		$gradient  = $tier_info['gradient'];
		$id        = 'badge-' . $tier . '-' . wp_rand( 1000, 9999 );

		$sparkle = '';
		if ( self::TIER_PLATINUM === $tier ) {
			$sparkle = '<path d="M32 8 L33.5 12 L38 12 L34.5 14.5 L36 19 L32 16 L28 19 L29.5 14.5 L26 12 L30.5 12 Z" fill="' . esc_attr( $color ) . '" opacity="0.8"/>';
		}

		return sprintf(
			'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="%1$d" height="%1$d" fill="none" class="agenticwp-badge-svg agenticwp-badge-svg--%2$s">
				<defs>
					<linearGradient id="%3$s-gradient" x1="0%%" y1="0%%" x2="100%%" y2="100%%">
						<stop offset="0%%" style="stop-color:%4$s"/>
						<stop offset="50%%" style="stop-color:%5$s"/>
						<stop offset="100%%" style="stop-color:%6$s"/>
					</linearGradient>
				</defs>
				<circle cx="32" cy="32" r="28" fill="url(#%3$s-gradient)"/>
				<circle cx="32" cy="32" r="24" fill="#FEFEFE" stroke="%5$s" stroke-width="2"/>
				<circle cx="32" cy="32" r="20" fill="none" stroke="%5$s" stroke-width="1" stroke-dasharray="3 3" opacity="0.5"/>
				%7$s
			</svg>',
			$size,
			esc_attr( $tier ),
			esc_attr( $id ),
			esc_attr( $gradient[0] ),
			esc_attr( $gradient[1] ),
			esc_attr( $gradient[2] ),
			$sparkle
		);
	}

	/**
	 * Gets category display information.
	 *
	 * @param string $category Category constant.
	 * @return array Category info with 'name', 'icon'.
	 */
	public function get_category_info( string $category ): array {
		$categories = array(
			self::CATEGORY_CONTENT  => array(
				'name' => __( 'Content Creator', 'agenticwp' ),
				'icon' => "\u{1F4DD}",
			),
			self::CATEGORY_SEO      => array(
				'name' => __( 'SEO Master', 'agenticwp' ),
				'icon' => "\u{1F50D}",
			),
			self::CATEGORY_STREAK   => array(
				'name' => __( 'Streak Champion', 'agenticwp' ),
				'icon' => "\u{1F525}",
			),
			self::CATEGORY_TOOLS    => array(
				'name' => __( 'Task Completion', 'agenticwp' ),
				'icon' => "\u{1F527}",
			),
			self::CATEGORY_DELEGATE => array(
				'name' => __( 'Delegation', 'agenticwp' ),
				'icon' => "\u{1F916}",
			),
		);

		return $categories[ $category ] ?? array(
			'name' => __( 'General', 'agenticwp' ),
			'icon' => "\u{1F3C6}",
		);
	}

	/**
	 * Gets summary statistics for achievements.
	 *
	 * @return array Summary with 'total', 'unlocked', 'by_tier', 'by_category'.
	 */
	public function get_achievement_summary(): array {
		$usage_stats = Usage_Stats::get_instance();
		$unlocked    = $usage_stats->get_achievements();

		$by_tier = array(
			self::TIER_BRONZE   => array(
				'total'    => 0,
				'unlocked' => 0,
			),
			self::TIER_SILVER   => array(
				'total'    => 0,
				'unlocked' => 0,
			),
			self::TIER_GOLD     => array(
				'total'    => 0,
				'unlocked' => 0,
			),
			self::TIER_PLATINUM => array(
				'total'    => 0,
				'unlocked' => 0,
			),
		);

		$by_category = array(
			self::CATEGORY_CONTENT  => array(
				'total'    => 0,
				'unlocked' => 0,
			),
			self::CATEGORY_SEO      => array(
				'total'    => 0,
				'unlocked' => 0,
			),
			self::CATEGORY_STREAK   => array(
				'total'    => 0,
				'unlocked' => 0,
			),
			self::CATEGORY_TOOLS    => array(
				'total'    => 0,
				'unlocked' => 0,
			),
			self::CATEGORY_DELEGATE => array(
				'total'    => 0,
				'unlocked' => 0,
			),
		);

		$total_unlocked = 0;
		$achievements   = $this->get_achievements();

		foreach ( $achievements as $id => $achievement ) {
			$tier     = $achievement['tier'];
			$category = $achievement['category'];

			++$by_tier[ $tier ]['total'];
			++$by_category[ $category ]['total'];

			if ( isset( $unlocked[ $id ] ) && null !== $unlocked[ $id ] ) {
				++$total_unlocked;
				++$by_tier[ $tier ]['unlocked'];
				++$by_category[ $category ]['unlocked'];
			}
		}

		return array(
			'total'       => count( $achievements ),
			'unlocked'    => $total_unlocked,
			'by_tier'     => $by_tier,
			'by_category' => $by_category,
		);
	}
}
