<?php
/**
 * Donor Wall Block
 *
 * Displays a mosaic of donor tiles for the current fundraiser (and all its
 * descendants). Each tile shows a CSS-generated initial avatar and the donor's
 * first name. Anonymous donors appear as a generic "Anonymous" tile (not hidden,
 * so the count remains honest) and are de-duplicated into a single tile showing
 * how many anonymous donors contributed.
 *
 * @package AmbassadorHub\Fundraisers\Blocks\DonorWall
 */

namespace AmbassadorHub\Fundraisers\Blocks\DonorWall;

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

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare

use AmbassadorHub\Donations\DonationsModule;
use AmbassadorHub\Donors\DonorsModule;
use AmbassadorHub\Fundraisers\Admin\Settings\SettingsManager;
use AmbassadorHub\Fundraisers\FundraisersModule;

/**
 * Donor Wall block registration and server-side rendering.
 */
final class Block {

	/** Palette for initial-avatar background colours (12 accessible colours). */
	private const AVATAR_COLOURS = array(
		'#1e6db4',
		'#0073aa',
		'#006505',
		'#8f461b',
		'#6e3a89',
		'#b34801',
		'#c83b3b',
		'#207171',
		'#7b4e00',
		'#2b5797',
		'#4a6741',
		'#6b3070',
	);

	// -------------------------------------------------------------------------
	// Boot
	// -------------------------------------------------------------------------

	/**
	 * Initialize the block.
	 *
	 * @return void
	 */
	public static function init(): void {
		add_action( 'init', array( self::class, 'register_block' ) );
	}

	// -------------------------------------------------------------------------
	// Registration
	// -------------------------------------------------------------------------

	/**
	 * Register block type, editor script, and styles.
	 *
	 * @return void
	 */
	public static function register_block(): void {
		$block_dir = AMBASSADOR_HUB_PATH . 'src/Fundraisers/Blocks/DonorWall/';
		$block_url = AMBASSADOR_HUB_URL . 'src/Fundraisers/Blocks/DonorWall/';

		$editor_handle = 'ambh-donor-wall-editor';
		$editor_file   = $block_dir . 'index.js';
		if ( file_exists( $editor_file ) ) {
			wp_register_script(
				$editor_handle,
				$block_url . 'index.js',
				array( 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-server-side-render' ),
				(string) filemtime( $editor_file ),
				true
			);
		}

		$style_file = $block_dir . 'style.css';
		wp_register_style(
			'ambh-donor-wall',
			$block_url . 'style.css',
			array(),
			file_exists( $style_file ) ? (string) filemtime( $style_file ) : AMBASSADOR_HUB_VERSION
		);

		$block_args = array(
			'style'           => 'ambh-donor-wall',
			'render_callback' => array( self::class, 'render_block' ),
		);

		if ( file_exists( $editor_file ) ) {
			$block_args['editor_script'] = $editor_handle;
		}

		register_block_type( $block_dir, $block_args );
	}

	// -------------------------------------------------------------------------
	// Rendering
	// -------------------------------------------------------------------------

	/**
	 * Server-side render callback.
	 *
	 * @param array $attributes Block attributes.
	 *
	 * @return string Rendered HTML.
	 */
	public static function render_block( array $attributes = array() ): string {

		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return '<div class="ambh-donor-wall ambh-block-preview">'
				. '<p class="ambh-block-preview__label">' . esc_html__( 'Donor Wall', 'ambassadorhub' ) . '</p>'
				. '<p class="ambh-block-preview__meta">' . esc_html__( 'Donor tiles render on the frontend.', 'ambassadorhub' ) . '</p>'
				. '</div>';
		}

		$max_items    = max( 1, min( 500, (int) ( $attributes['maxItems'] ?? 50 ) ) );
		$columns      = max( 2, min( 10, (int) ( $attributes['columns'] ?? 5 ) ) );
		$show_amounts = ! empty( $attributes['showAmounts'] );
		$title        = isset( $attributes['title'] ) && '' !== $attributes['title']
			? $attributes['title']
			: '';

		$current_id = (int) get_the_ID();

		if ( FundraisersModule::POST_TYPE !== get_post_type( $current_id ) ) {
			return '';
		}

		$donors = self::get_donors( $current_id, $max_items, $show_amounts );

		if ( empty( $donors ) ) {
			return '';
		}

		ob_start();
		?>
		<div class="ambh-donor-wall" style="--ambh-dw-columns:<?php echo esc_attr( (string) $columns ); ?>;">
			<?php if ( $title ) : ?>
				<h3 class="ambh-dw-title"><?php echo esc_html( $title ); ?></h3>
			<?php endif; ?>
			<ul class="ambh-dw-grid">
				<?php foreach ( $donors as $donor ) : ?>
					<li class="ambh-dw-tile<?php echo $donor['is_anonymous'] ? ' is-anonymous' : ''; ?>">
						<div class="ambh-dw-tile__avatar" style="--ambh-dw-avatar-bg:<?php echo esc_attr( $donor['color'] ); ?>;" aria-hidden="true">
							<?php echo esc_html( $donor['initial'] ); ?>
						</div>
						<span class="ambh-dw-tile__name"><?php echo esc_html( $donor['display_name'] ); ?></span>
						<?php if ( $show_amounts && $donor['total'] > 0 ) : ?>
							<span class="ambh-dw-tile__amount"><?php echo esc_html( SettingsManager::format_currency( $donor['total'] ) ); ?></span>
						<?php endif; ?>
					</li>
				<?php endforeach; ?>
			</ul>
		</div>
		<?php
		return ob_get_clean();
	}

	// -------------------------------------------------------------------------
	// Data
	// -------------------------------------------------------------------------

	/**
	 * Fetch and deduplicate donors for a fundraiser and all its descendants.
	 *
	 * @param int  $fundraiser_id Fundraiser ID.
	 * @param int  $max_items     Maximum number of tiles to return.
	 * @param bool $show_amounts  Whether total amounts are needed.
	 *
	 * @return array List of donor data maps.
	 */
	private static function get_donors( int $fundraiser_id, int $max_items, bool $show_amounts ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- kept for API symmetry with callers that pass the block's showAmounts attribute; amount totals are always computed, display gating happens in the render layer.
		global $wpdb;

		$table        = DonationsModule::get_table_name();
		$donors_table = DonorsModule::get_table_name();

		$all_ids      = self::get_all_fundraiser_ids( $fundraiser_id );
		$placeholders = implode( ', ', array_fill( 0, count( $all_ids ), '%d' ) );

		$rows = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT dn.name AS donor_name, d.is_anonymous, SUM(d.amount) as total
				 FROM {$table} d
				 LEFT JOIN {$donors_table} dn ON dn.id = d.donor_id
				 WHERE d.fundraiser_id IN ({$placeholders})
				   AND d.status = 'completed'
				 GROUP BY d.donor_id, d.is_anonymous
				 ORDER BY MIN(d.created_at) ASC
				 LIMIT %d",
				array_merge( $all_ids, array( $max_items ) )
			)
		);

		if ( empty( $rows ) ) {
			return array();
		}

		// Build display list. Collapse all anonymous rows into a single tile.
		$donors     = array();
		$anon_count = 0;
		$anon_total = 0.0;

		foreach ( $rows as $row ) {
			if ( 1 === (int) $row->is_anonymous || '' === trim( (string) $row->donor_name ) ) {
				++$anon_count;
				$anon_total += (float) $row->total;
				continue;
			}

			$name      = trim( (string) $row->donor_name );
			$initial   = mb_strtoupper( mb_substr( $name, 0, 1 ) );
			$color_idx = abs( crc32( $name ) ) % count( self::AVATAR_COLOURS );

			$donors[] = array(
				'display_name' => $name,
				'initial'      => $initial,
				'color'        => self::AVATAR_COLOURS[ $color_idx ],
				'total'        => (float) $row->total,
				'is_anonymous' => false,
			);
		}

		// Add a single anonymous tile if there were any anonymous donors.
		if ( $anon_count > 0 ) {
			$donors[] = array(
				'display_name' => $anon_count > 1
					? sprintf(
						/* translators: %d: number of anonymous donors */
						_n( '%d anonymous donor', '%d anonymous donors', $anon_count, 'ambassadorhub' ),
						$anon_count
					)
					: __( 'Anonymous', 'ambassadorhub' ),
				'initial'      => '?',
				'color'        => '#8c8f94',
				'total'        => $anon_total,
				'is_anonymous' => true,
			);
		}

		return $donors;
	}

	/**
	 * Recursively collect all fundraiser IDs in the subtree rooted at $parent_id.
	 *
	 * @param int $parent_id Root fundraiser post ID.
	 *
	 * @return int[]
	 */
	private static function get_all_fundraiser_ids( int $parent_id ): array {
		$ids      = array( $parent_id );
		$children = get_posts(
			array(
				'post_type'      => FundraisersModule::POST_TYPE,
				'post_parent'    => $parent_id,
				'posts_per_page' => -1,
				'fields'         => 'ids',
				'post_status'    => 'publish',
			)
		);

		foreach ( $children as $child_id ) {
			$ids = array_merge( $ids, self::get_all_fundraiser_ids( (int) $child_id ) );
		}

		return $ids;
	}
}
