<?php

declare(strict_types=1);

namespace Australcode\Rewards\Frontend;

use Australcode\Rewards\Domain\Settings\SettingsRepository;
use Australcode\Rewards\Service\EarningCalculator;
use Australcode\Rewards\Service\RedemptionService;
use Australcode\Rewards\Service\WalletService;
use WC_Product;

defined( 'ABSPATH' ) || exit;

/**
 * Customer-facing shortcodes (also exposed as Gutenberg blocks via Blocks):
 *  - [acrwd_balance]       current points balance        (atts: label, class)
 *  - [acrwd_history]       recent points activity table  (atts: limit, class)
 *  - [acrwd_earn_message]  "earn X points" for a product (atts: product_id, class)
 *  - [acrwd_redeem]        "use your points" cart form
 *
 * The balance/history/redeem shortcodes render nothing for logged-out visitors.
 * These give page builders (Elementor/Bricks/Divi) and FSE a baseline way to
 * place every rewards surface without native widgets.
 */
final class Shortcodes {

	public function __construct(
		private readonly WalletService $wallet,
		private readonly SettingsRepository $settings,
		private readonly EarningCalculator $calculator,
		private readonly RedeemFormRenderer $redeem_form,
		private readonly RedemptionService $redemption,
	) {
	}

	public function register(): void {
		add_shortcode( 'acrwd_balance', array( $this, 'render_balance' ) );
		add_shortcode( 'acrwd_history', array( $this, 'render_history' ) );
		add_shortcode( 'acrwd_earn_message', array( $this, 'render_earn_message' ) );
		add_shortcode( 'acrwd_redeem', array( $this, 'render_redeem' ) );
	}

	/**
	 * @param array<string,mixed>|string $atts
	 */
	public function render_balance( $atts ): string {
		if ( ! is_user_logged_in() ) {
			return '';
		}

		$atts = shortcode_atts(
			array(
				'label' => 'yes',
				'value' => 'no',
				'class' => '',
			),
			is_array( $atts ) ? $atts : array(),
			'acrwd_balance'
		);

		$balance  = $this->wallet->get_balance( get_current_user_id() );
		$settings = $this->settings->get();
		$label    = 1 === $balance->points_balance ? $settings->points_label_singular : $settings->points_label_plural;

		// Opt-in money value (value="yes"): shows what the balance is worth, reusing
		// the redemption rate — the same "show the effect" idea as the admin preview.
		$value_html = '';
		if ( $this->truthy( $atts['value'] ) && $settings->redemption_enabled ) {
			$amount = $this->redemption->points_to_amount( $balance->points_balance );
			if ( $amount > 0 ) {
				$value_html = ' <span class="acrwd-balance-value">' . wp_kses_post( wc_price( $amount ) ) . '</span>';
			}
		}

		return sprintf(
			'<span class="%s">%s%s%s</span>',
			esc_attr( $this->css_classes( 'acrwd-balance', (string) $atts['class'] ) ),
			esc_html( number_format_i18n( $balance->points_balance ) ),
			$this->truthy( $atts['label'] ) ? ' ' . esc_html( $label ) : '',
			$value_html
		);
	}

	/**
	 * @param array<string,mixed>|string $atts
	 */
	public function render_history( $atts ): string {
		if ( ! is_user_logged_in() ) {
			return '';
		}

		$atts = shortcode_atts(
			array(
				'limit' => 20,
				'class' => '',
			),
			is_array( $atts ) ? $atts : array(),
			'acrwd_history'
		);

		$limit   = max( 1, min( 100, (int) $atts['limit'] ) );
		$entries = $this->wallet->get_transactions( get_current_user_id(), $limit );
		if ( array() === $entries ) {
			return '<p class="acrwd-history-empty">' . esc_html__( 'No points activity yet.', 'australcode-points-rewards' ) . '</p>';
		}

		$rows = '';
		foreach ( $entries as $entry ) {
			// U+2212 minus matches the admin ledger glyph (CustomersPage::signed()).
			$delta = ( $entry->points_delta < 0 ? '−' : '+' ) . number_format_i18n( abs( $entry->points_delta ) );
			// created_at is stored in UTC; render in the store timezone, site format.
			$when  = null !== $entry->created_at
				? wp_date( (string) get_option( 'date_format', 'Y-m-d' ), (int) strtotime( $entry->created_at . ' UTC' ) )
				: '—';
			$rows .= sprintf(
				'<tr><td>%s</td><td>%s</td><td class="acrwd-delta">%s</td></tr>',
				esc_html( (string) $when ),
				esc_html( $entry->reason ),
				esc_html( $delta )
			);
		}

		return sprintf(
			'<table class="%s"><thead><tr><th>%s</th><th>%s</th><th>%s</th></tr></thead><tbody>%s</tbody></table>',
			esc_attr( $this->css_classes( 'acrwd-history', (string) $atts['class'] ) ),
			esc_html__( 'Date', 'australcode-points-rewards' ),
			esc_html__( 'Activity', 'australcode-points-rewards' ),
			esc_html__( 'Points', 'australcode-points-rewards' ),
			$rows
		);
	}

	/**
	 * "Earn X points with this purchase" for a product. Defaults to the current
	 * product (single product page / loop); pass product_id to target another.
	 *
	 * @param array<string,mixed>|string $atts
	 */
	public function render_earn_message( $atts ): string {
		$settings = $this->settings->get();
		if ( ! $settings->earning_enabled ) {
			return '';
		}

		$atts = shortcode_atts(
			array(
				'product_id' => 0,
				'class'      => '',
			),
			is_array( $atts ) ? $atts : array(),
			'acrwd_earn_message'
		);

		$product = $this->resolve_product( (int) $atts['product_id'] );
		if ( ! $product instanceof WC_Product ) {
			return '';
		}

		$price  = (float) wc_get_price_to_display( $product );
		$points = $this->calculator->points_for_amount( $price, $product->get_id() );
		if ( $points <= 0 ) {
			return '';
		}

		$label = 1 === $points ? $settings->points_label_singular : $settings->points_label_plural;

		return sprintf(
			'<p class="%s">%s</p>',
			esc_attr( $this->css_classes( 'acrwd-points-message', (string) $atts['class'] ) ),
			wp_kses_post(
				sprintf(
					/* translators: 1: points amount, 2: points label (e.g. "points"). */
					__( 'Earn <strong>%1$s %2$s</strong> with this purchase.', 'australcode-points-rewards' ),
					esc_html( number_format_i18n( $points ) ),
					esc_html( $label )
				)
			)
		);
	}

	/**
	 * "Use your points" redeem form for the current cart (shared renderer).
	 *
	 * @param array<string,mixed>|string $atts
	 */
	public function render_redeem( $atts ): string {
		unset( $atts );
		if ( ! is_user_logged_in() ) {
			return '';
		}

		ob_start();
		$this->redeem_form->render( get_current_user_id() );
		return (string) ob_get_clean();
	}

	/**
	 * Resolves a product from an explicit id, falling back to the current global
	 * product (single product page or loop).
	 */
	private function resolve_product( int $product_id ): ?WC_Product {
		if ( $product_id > 0 ) {
			$product = wc_get_product( $product_id );
			return $product instanceof WC_Product ? $product : null;
		}

		global $product;
		return $product instanceof WC_Product ? $product : null;
	}

	/** Base class plus any sanitized extra classes from a `class` attribute. */
	private function css_classes( string $base, string $extra ): string {
		$classes = array_filter( array_map( 'sanitize_html_class', explode( ' ', $extra ) ) );
		return trim( $base . ' ' . implode( ' ', $classes ) );
	}

	/** Treats "no"/"false"/"0"/"" as off, everything else as on. */
	private function truthy( mixed $value ): bool {
		return ! in_array( strtolower( (string) $value ), array( 'no', 'false', '0', '' ), true );
	}
}
