<?php
/**
 * Renderer for non-form shortcode widgets.
 *
 * @package Donatico\Frontend
 */

declare(strict_types=1);

namespace Donatico\Frontend;

use Donatico\Database\DonationRepository;
use Donatico\Helpers\Currency;
use Donatico\Models\Donation;

/**
 * Renders the total-raised, donation-count, recent-donors, and progress-bar
 * shortcodes. All output is fully escaped.
 */
class WidgetRenderer {

	/** @var DonationRepository */
	private DonationRepository $repository;

	/**
	 * @param DonationRepository $repository
	 */
	public function __construct( DonationRepository $repository ) {
		$this->repository = $repository;
	}

	// -------------------------------------------------------------------------
	// [donatico_total_raised]
	// -------------------------------------------------------------------------

	/**
	 * Render the total amount raised.
	 *
	 * @param array<string, mixed> $atts
	 * @return string
	 */
	public function totalRaised( array $atts ): string {
		$format   = isset( $atts['format'] ) && $atts['format'] === 'number' ? 'number' : 'currency';
		$currency = (string) get_option( 'donatico_currency', 'USD' );

		$args = array( 'status' => Donation::STATUS_COMPLETED );
		if ( ! empty( $atts['since'] ) ) {
			// Date filter handled in a future query extension; ignored for now.
		}

		$stats = $this->repository->getStats();
		$cents = $stats['total_amount'];

		if ( $format === 'currency' ) {
			$display = esc_html( Currency::formatCents( $cents, $currency ) );
		} else {
			$display = esc_html( number_format( $cents / 100, 2 ) );
		}

		return '<span class="donatico-total-raised">' . $display . '</span>';
	}

	// -------------------------------------------------------------------------
	// [donatico_donation_count]
	// -------------------------------------------------------------------------

	/**
	 * Render the total number of completed donations.
	 *
	 * @param array<string, mixed> $atts
	 * @return string
	 */
	public function donationCount( array $atts ): string {
		$format          = isset( $atts['format'] ) && $atts['format'] === 'text' ? 'text' : 'number';
		$text_singular   = sanitize_text_field( (string) ( $atts['text_singular'] ?? __( 'donation', 'donatico' ) ) );
		$text_plural     = sanitize_text_field( (string) ( $atts['text_plural'] ?? __( 'donations', 'donatico' ) ) );

		$count = $this->repository->count( array( 'status' => Donation::STATUS_COMPLETED ) );

		if ( $format === 'text' ) {
			$label   = $count === 1 ? $text_singular : $text_plural;
			$display = esc_html( number_format( $count ) . ' ' . $label );
		} else {
			$display = esc_html( number_format( $count ) );
		}

		return '<span class="donatico-donation-count">' . $display . '</span>';
	}

	// -------------------------------------------------------------------------
	// [donatico_recent_donors]
	// -------------------------------------------------------------------------

	/**
	 * Render a list of recent completed donors.
	 *
	 * @param array<string, mixed> $atts
	 * @return string
	 */
	public function recentDonors( array $atts ): string {
		$limit          = max( 1, min( 50, absint( $atts['limit'] ?? 5 ) ) );
		$show_amount    = ( ( $atts['show_amount'] ?? 'yes' ) !== 'no' );
		$show_message   = ( ( $atts['show_message'] ?? 'yes' ) !== 'no' );
		$show_date      = ( ( $atts['show_date'] ?? 'yes' ) !== 'no' );
		$hide_anonymous = ( ( $atts['hide_anonymous'] ?? 'yes' ) !== 'no' );

		$currency = (string) get_option( 'donatico_currency', 'USD' );

		$donors = $this->repository->findAll(
			array(
				'status'  => Donation::STATUS_COMPLETED,
				'orderby' => 'created_at',
				'order'   => 'DESC',
				'limit'   => $hide_anonymous ? $limit * 2 : $limit, // over-fetch when filtering
			)
		);

		if ( empty( $donors ) ) {
			return '<p class="donatico-no-donors">' . esc_html__( 'No donations yet. Be the first!', 'donatico' ) . '</p>';
		}

		$shown = 0;
		$html  = '<ul class="donatico-recent-donors">';

		foreach ( $donors as $donor ) {
			if ( $shown >= $limit ) {
				break;
			}

			if ( $hide_anonymous && $donor->isAnonymous() ) {
				continue;
			}

			$name      = $donor->getDisplayName();
			$initial   = mb_strtoupper( mb_substr( $name, 0, 1 ) );
			$color     = $this->avatarColor( $donor->getDonorEmail() );
			$time_ago  = $this->timeAgo( $donor->getCreatedAt() );

			$html .= '<li class="donatico-donor-item">';

			// Avatar — initial letter circle (no Gravatar to protect donor privacy).
			$html .= sprintf(
				'<span class="donatico-donor-avatar" style="background:%s" aria-hidden="true">%s</span>',
				esc_attr( $color ),
				esc_html( $initial )
			);

			$html .= '<span class="donatico-donor-details">';
			$html .= '<span class="donatico-donor-name">' . esc_html( $name ) . '</span>';

			if ( $show_amount ) {
				$html .= ' <span class="donatico-donor-amount">'
					. esc_html( Currency::formatCents( $donor->getAmount(), $currency ) )
					. '</span>';
			}

			if ( $show_date ) {
				$html .= ' <span class="donatico-donor-date">' . esc_html( $time_ago ) . '</span>';
			}

			if ( $show_message && $donor->getMessage() ) {
				$html .= '<span class="donatico-donor-message">' . esc_html( $donor->getMessage() ) . '</span>';
			}

			$html .= '</span>'; // .donatico-donor-details
			$html .= '</li>';

			$shown++;
		}

		$html .= '</ul>';

		return $html;
	}

	// -------------------------------------------------------------------------
	// [donatico_progress_bar]
	// -------------------------------------------------------------------------

	/**
	 * Render a standalone donation progress bar.
	 *
	 * @param array<string, mixed> $atts
	 * @return string
	 */
	public function progressBar( array $atts ): string {
		$goal = isset( $atts['goal'] ) && $atts['goal'] !== ''
			? absint( $atts['goal'] )
			: (int) get_option( 'donatico_goal_amount', 0 );

		if ( $goal <= 0 ) {
			return '';
		}

		$show_amount     = ( ( $atts['show_amount'] ?? 'yes' ) !== 'no' );
		$show_percentage = ( ( $atts['show_percentage'] ?? 'yes' ) !== 'no' );

		$currency     = (string) get_option( 'donatico_currency', 'USD' );
		$stats        = $this->repository->getStats();
		$raised_cents = $stats['total_amount'];
		$raised_major = $raised_cents / 100;
		$progress     = min( 100, (int) round( ( $raised_major / $goal ) * 100 ) );
		$is_complete  = $progress >= 100;

		$modifier = $is_complete ? ' donatico-progress--complete' : '';

		$html  = '<div class="donatico-progress-widget' . esc_attr( $modifier ) . '">';
		$html .= '<div class="donatico-progress-track" role="progressbar" aria-valuenow="' . esc_attr( (string) $progress ) . '" aria-valuemin="0" aria-valuemax="100">';
		$html .= '<div class="donatico-progress-fill" style="transform:scaleX(' . esc_attr( number_format( $progress / 100, 4 ) ) . ')"></div>';
		$html .= '</div>';

		if ( $show_amount || $show_percentage ) {
			$html .= '<p class="donatico-progress-meta">';

			if ( $show_amount ) {
				$html .= sprintf(
					/* translators: 1: amount raised, 2: goal amount */
					esc_html__( '%1$s raised of %2$s', 'donatico' ),
					'<strong>' . esc_html( Currency::formatCents( $raised_cents, $currency ) ) . '</strong>',
					'<strong>' . esc_html( Currency::getSymbol( $currency ) . number_format( $goal, 2 ) ) . '</strong>'
				);
			}

			if ( $show_percentage ) {
				$html .= ' <span class="donatico-progress-pct">(' . esc_html( $progress . '%' ) . ')</span>';
			}

			$html .= '</p>';
		}

		$html .= '</div>';

		return $html;
	}

	// -------------------------------------------------------------------------
	// Helpers
	// -------------------------------------------------------------------------

	/**
	 * Generate a deterministic pastel background colour from an email string.
	 * The same email always yields the same colour — privacy-safe avatar.
	 *
	 * @param string $email
	 * @return string HSL CSS value.
	 */
	private function avatarColor( string $email ): string {
		$hash = crc32( $email );
		$hue  = abs( $hash ) % 360;
		return 'hsl(' . $hue . ',60%,55%)';
	}

	/**
	 * Convert a MySQL datetime string to a human-readable "time ago" string.
	 *
	 * @param string $datetime MySQL datetime e.g. "2024-03-01 14:00:00".
	 * @return string
	 */
	private function timeAgo( string $datetime ): string {
		$timestamp = strtotime( $datetime );
		if ( ! $timestamp ) {
			return '';
		}

		$diff = time() - $timestamp;

		if ( $diff < 60 ) {
			return __( 'just now', 'donatico' );
		} elseif ( $diff < 3600 ) {
			$mins = (int) round( $diff / 60 );
			/* translators: %d: number of minutes */
			return sprintf( _n( '%d minute ago', '%d minutes ago', $mins, 'donatico' ), $mins );
		} elseif ( $diff < 86400 ) {
			$hours = (int) round( $diff / 3600 );
			/* translators: %d: number of hours */
			return sprintf( _n( '%d hour ago', '%d hours ago', $hours, 'donatico' ), $hours );
		} else {
			$days = (int) round( $diff / 86400 );
			/* translators: %d: number of days */
			return sprintf( _n( '%d day ago', '%d days ago', $days, 'donatico' ), $days );
		}
	}
}
