<?php
/**
 * Activity Feed Block
 *
 * Renders a complete chronological timeline of:
 *   - All completed donations (newest-first display)
 *   - Fixed milestone events: fundraiser started, first donation, 25%, 50%, 75%, 100%
 *
 * Between two consecutive milestones, if there are more than MAX_EXPANDED
 * donations, the middle ones are wrapped in a native <details> element so
 * they are collapsed by default (no JS required).
 *
 * Returns empty string when no fundraiser context can be resolved.
 *
 * @package AmbassadorHub\Fundraisers\Blocks\ActivityFeed
 */

namespace AmbassadorHub\Fundraisers\Blocks\ActivityFeed;

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\Fundraisers\Admin\Settings\SettingsManager;
use AmbassadorHub\Fundraisers\FundraisersModule;

/**
 * Activity Feed block registration and server-side rendering.
 */
final class Block {

	/**
	 * When a run of donations between two milestones exceeds this count, the
	 * excess ones are wrapped in a collapsed <details> toggle.
	 * The first MAX_EXPANDED donations are always visible; the rest collapse.
	 */
	private const MAX_EXPANDED = 3;

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

	public static function init(): void {
		add_action( 'init', array( self::class, 'register_block' ) );
	}

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

	public static function register_block(): void {
		$block_dir = AMBASSADOR_HUB_PATH . 'src/Fundraisers/Blocks/ActivityFeed/';
		$block_url = AMBASSADOR_HUB_URL . 'src/Fundraisers/Blocks/ActivityFeed/';

		$editor_handle = 'ambh-activity-feed-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-activity-feed',
			$block_url . 'style.css',
			array(),
			file_exists( $style_file ) ? (string) filemtime( $style_file ) : AMBASSADOR_HUB_VERSION
		);

		$block_args = array(
			'style'           => 'ambh-activity-feed',
			'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
	// -------------------------------------------------------------------------

	public static function render_block( array $attributes = array() ): string {

		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return '<div class="ambh-activity-feed ambh-block-preview">'
				. '<p class="ambh-block-preview__label">' . esc_html__( 'Activity Feed', 'ambassadorhub' ) . '</p>'
				. '<p class="ambh-block-preview__meta">' . esc_html__( 'Donations and milestone events render on the frontend.', 'ambassadorhub' ) . '</p>'
				. '</div>';
		}

		$current_id    = (int) get_the_ID();
		$fundraiser_id = ( FundraisersModule::POST_TYPE === get_post_type( $current_id ) ) ? $current_id : 0;

		if ( ! $fundraiser_id ) {
			return '';
		}

		$show_messages   = $attributes['showMessages'] ?? true;
		$show_milestones = $attributes['showMilestones'] ?? true;

		// Gather all fundraiser IDs in the subtree.
		$all_ids = self::get_all_fundraiser_ids( $fundraiser_id );

		// Fetch ALL completed donations — no limit.
		$donations = DonationsModule::get_by_fundraisers( $all_ids, 'completed' );

		// Build the complete ordered timeline (newest-first items, milestones injected).
		$items = self::build_timeline( $donations, $fundraiser_id, $show_milestones );

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

		ob_start();
		?>
		<div class="ambh-activity-feed">
			<ul class="ambh-af-list">
				<?php self::render_items( $items, (bool) $show_messages, $fundraiser_id ); ?>
			</ul>
		</div>
		<?php
		return ob_get_clean();
	}

	// -------------------------------------------------------------------------
	// Timeline construction
	// -------------------------------------------------------------------------

	/**
	 * Build the complete timeline array with milestones and collapse groups.
	 *
	 * Timeline is newest-first. Each entry is:
	 *   [ 'type' => 'donation'|'milestone'|'collapse-start'|'collapse-end', 'data' => ... ]
	 *
	 * @param object[] $donations     All completed donations, newest-first.
	 * @param int      $fundraiser_id Root fundraiser ID.
	 * @param bool     $milestones_on Whether to inject milestone events.
	 *
	 * @return array
	 */
	private static function build_timeline( array $donations, int $fundraiser_id, bool $milestones_on ): array {

		// Always add the "fundraiser started" pseudo-item as the oldest event.
		$post = get_post( $fundraiser_id );

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

		if ( ! $milestones_on ) {
			// No milestone injection.
			return array_map(
				static fn( $d ) => array(
					'type' => 'donation',
					'data' => $d,
				),
				$donations
			);
		}

		// ------------------------------------------------------------------
		// Determine milestone crossing points (oldest-first pass).
		// ------------------------------------------------------------------

		$target = (float) get_post_meta( $fundraiser_id, '_ambh_target_amount', true );

		// Fixed milestone set: first-donation, 25%, 50%, 75%, 100%.
		$thresholds = array(
			1   => 'first',
			25  => 'pct',
			50  => 'pct',
			75  => 'pct',
			100 => 'pct',
		);

		// Reverse to oldest-first for the crossing-detection pass.
		$oldest_first = array_reverse( $donations );

		$crossing   = array(); // threshold key => donation ID that crossed it.
		$running    = 0.0;
		$first_done = false;

		foreach ( $oldest_first as $donation ) {
			$running += (float) $donation->amount;

			// "First donation" milestone — triggered by the very first donation.
			if ( ! $first_done ) {
				$crossing['first'] = $donation->id;
				$first_done        = true;
			}

			// Percentage milestones — only meaningful when a target is set.
			if ( $target > 0 ) {
				$pct = ( $running / $target ) * 100;
				foreach ( array( 25, 50, 75, 100 ) as $threshold ) {
					if ( ! isset( $crossing[ $threshold ] ) && $pct >= $threshold ) {
						$crossing[ $threshold ] = $donation->id;
					}
				}
			}
		}

		// ------------------------------------------------------------------
		// Build a flat merged list (oldest-first), milestone after the
		// donation that triggered it, then reverse to newest-first.
		// ------------------------------------------------------------------

		$flat       = array();
		$running    = 0.0;
		$first_done = false;

		foreach ( $oldest_first as $donation ) {
			$running += (float) $donation->amount;
			$flat[]   = array(
				'type' => 'donation',
				'data' => $donation,
			);

			// First donation milestone.
			if ( ! $first_done && isset( $crossing['first'] ) && (int) $donation->id === (int) $crossing['first'] ) {
				$first_done = true;
				$flat[]     = array(
					'type' => 'milestone',
					'data' => array(
						'kind'        => 'first',
						'percentage'  => null,
						'label'       => __( 'First donation received!', 'ambassadorhub' ),
						'achieved_at' => $donation->created_at,
					),
				);
			}

			// Percentage milestones.
			if ( $target > 0 ) {
				foreach ( array( 25, 50, 75, 100 ) as $threshold ) {
					if ( isset( $crossing[ $threshold ] ) && (int) $donation->id === (int) $crossing[ $threshold ] ) {
						$flat[] = array(
							'type' => 'milestone',
							'data' => array(
								'kind'        => 'pct',
								'percentage'  => $threshold,
								'label'       => sprintf(
									/* translators: %d: percentage */
									__( '%d%% of goal reached!', 'ambassadorhub' ),
									$threshold
								),
								'achieved_at' => $donation->created_at,
							),
						);
					}
				}
			}
		}

		// Newest-first.
		$flat = array_reverse( $flat );

		// ------------------------------------------------------------------
		// Group donations between milestones and wrap oversize groups.
		// ------------------------------------------------------------------

		return self::apply_collapse_groups( $flat );
	}

	/**
	 * Walk the timeline and wrap donation runs that exceed MAX_EXPANDED with
	 * collapse markers (rendered as <details>/<summary> in HTML).
	 *
	 * A "run" is a consecutive sequence of donation items between two
	 * milestone items.
	 *
	 * @param array $items Flat timeline (newest-first).
	 *
	 * @return array Timeline with 'collapse-start' / 'collapse-end' wrappers added.
	 */
	private static function apply_collapse_groups( array $items ): array {
		$out = array();
		$run = array(); // current donation run

		$flush_run = static function () use ( &$out, &$run ): void {
			$count = count( $run );
			if ( $count > self::MAX_EXPANDED ) {
				// Show the MAX_EXPANDED most-recent donations (index 0 = newest), collapse the rest.
				foreach ( array_slice( $run, 0, self::MAX_EXPANDED ) as $item ) {
					$out[] = $item;
				}
				$hidden = $count - self::MAX_EXPANDED;
				$out[]  = array(
					'type' => 'collapse-start',
					'data' => array( 'count' => $hidden ),
				);
				foreach ( array_slice( $run, self::MAX_EXPANDED ) as $item ) {
					$out[] = $item;
				}
				$out[] = array(
					'type' => 'collapse-end',
					'data' => array(),
				);
			} else {
				foreach ( $run as $item ) {
					$out[] = $item;
				}
			}
			$run = array();
		};

		foreach ( $items as $item ) {
			if ( 'donation' === $item['type'] ) {
				$run[] = $item;
			} else {
				// Milestone or wrapper — flush the pending donation run first.
				$flush_run();
				$out[] = $item;
			}
		}

		// Flush any trailing run.
		$flush_run();

		return $out;
	}

	// -------------------------------------------------------------------------
	// Item rendering
	// -------------------------------------------------------------------------

	/**
	 * Render all timeline items, handling collapse wrappers.
	 *
	 * @param array $items         Timeline items.
	 * @param bool  $show_messages Whether to show donor messages.
	 * @param int   $root_id       Root fundraiser ID.
	 *
	 * @return void
	 */
	private static function render_items( array $items, bool $show_messages, int $root_id ): void {
		foreach ( $items as $item ) {
			switch ( $item['type'] ) {
				case 'donation':
					self::render_donation_item( $item['data'], $show_messages, $root_id );
					break;

				case 'milestone':
					self::render_milestone_item( $item['data'] );
					break;

				case 'collapse-start':
					$hidden_count = $item['data']['count'];
					echo '<li class="ambh-af-item ambh-af-item--collapse">';
					echo '<details class="ambh-af-collapse">';
					echo '<summary class="ambh-af-collapse__toggle">';
					echo '<span class="ambh-af-collapse__icon" aria-hidden="true">'
						. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">'
						. '<path fill-rule="evenodd" d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06z" clip-rule="evenodd"/>'
						. '</svg>'
						. '</span>';
					echo esc_html(
						sprintf(
							/* translators: %d: number of hidden donations */
							_n( 'Show %d more donation', 'Show %d more donations', $hidden_count, 'ambassadorhub' ),
							$hidden_count
						)
					);
					echo '</summary>';
					echo '<ul class="ambh-af-collapse__list">';
					break;

				case 'collapse-end':
					echo '</ul></details></li>';
					break;
			}
		}
	}

	/**
	 * Render a single donation timeline item.
	 *
	 * @param object $donation      Donation DB row.
	 * @param bool   $show_messages Whether to show the donor message.
	 * @param int    $root_id       Root fundraiser ID.
	 *
	 * @return void
	 */
	private static function render_donation_item( object $donation, bool $show_messages, int $root_id ): void {
		$donor_name = DonationsModule::get_donor_name_for_display( $donation );
		$amount     = SettingsManager::format_currency( (float) $donation->amount, 0 );
		$date_str   = date_i18n( get_option( 'date_format' ), strtotime( $donation->created_at ) );
		$via        = '';

		if ( (int) $donation->fundraiser_id !== $root_id ) {
			$child_post = get_post( (int) $donation->fundraiser_id );
			if ( $child_post ) {
				$user = get_user_by( 'ID', (int) $child_post->post_author );
				if ( $user ) {
					$via = $user->display_name;
				}
			}
		}
		?>
		<li class="ambh-af-item ambh-af-item--donation">
			<span class="ambh-af-item__icon" aria-hidden="true">
				<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
					<path d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z"/>
				</svg>
			</span>
			<div class="ambh-af-item__content">
				<p class="ambh-af-item__headline">
					<span class="ambh-af-donor"><?php echo esc_html( $donor_name ); ?></span>
					<span class="ambh-af-amount"><?php echo esc_html( $amount ); ?></span>
					<?php if ( $via ) : ?>
						<span class="ambh-af-via">
						<?php
						/* translators: %s: display name of the user who received the donation */
						echo esc_html( sprintf( __( 'via %s', 'ambassadorhub' ), $via ) );
						?>
						</span>
					<?php endif; ?>
				</p>
				<?php if ( $show_messages && ! empty( $donation->message ) ) : ?>
					<p class="ambh-af-message"><?php echo esc_html( $donation->message ); ?></p>
				<?php endif; ?>
				<time class="ambh-af-date" datetime="<?php echo esc_attr( $donation->created_at ); ?>"><?php echo esc_html( $date_str ); ?></time>
			</div>
		</li>
		<?php
	}

	/**
	 * Render a milestone timeline item.
	 *
	 * @param array $milestone Milestone data.
	 *
	 * @return void
	 */
	private static function render_milestone_item( array $milestone ): void {
		$kind = $milestone['kind'] ?? 'pct';
		?>
		<li class="ambh-af-item ambh-af-item--milestone ambh-af-item--milestone-<?php echo esc_attr( $kind ); ?>">
			<span class="ambh-af-item__icon" aria-hidden="true">
				<?php if ( 'start' === $kind ) : ?>
					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M3 6a3 3 0 0 1 3-3h10a1 1 0 0 1 .8 1.6L14.25 7l2.55 2.4A1 1 0 0 1 16 11H6a1 1 0 0 1-1-1V6Z" clip-rule="evenodd"/>
					</svg>
				<?php elseif ( 'first' === $kind ) : ?>
					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 0 0 .95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 0 0-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 0 0-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 0 0-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 0 0 .951-.69l1.07-3.292Z"/>
					</svg>
				<?php else : ?>
					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd"/>
					</svg>
				<?php endif; ?>
			</span>
			<div class="ambh-af-item__content">
				<p class="ambh-af-item__headline">
					<span class="ambh-af-milestone-label"><?php echo esc_html( $milestone['label'] ); ?></span>
				</p>
				<?php if ( ! empty( $milestone['achieved_at'] ) ) : ?>
					<time class="ambh-af-date" datetime="<?php echo esc_attr( $milestone['achieved_at'] ); ?>">
						<?php echo esc_html( date_i18n( get_option( 'date_format' ), strtotime( $milestone['achieved_at'] ) ) ); ?>
					</time>
				<?php endif; ?>
			</div>
		</li>
		<?php
	}

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

	/**
	 * Fetch ALL completed donations for a set of fundraiser IDs (no limit).
	 *
	 * Returns newest-first.
	 *
	 * @param int[] $fundraiser_ids Fundraiser IDs to fetch for.
	 *
	 * @return object[]
	 */
	private static function get_all_donations( array $fundraiser_ids ): array {
		global $wpdb;

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

		$table        = DonationsModule::get_table_name();
		$placeholders = implode( ', ', array_fill( 0, count( $fundraiser_ids ), '%d' ) );

		$rows = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT id, fundraiser_id, donor_name, is_anonymous, amount, currency, message, created_at
				 FROM {$table}
				 WHERE fundraiser_id IN ({$placeholders})
				   AND status = 'completed'
				 ORDER BY created_at DESC",
				$fundraiser_ids
			)
		);

		return $rows ? $rows : array();
	}

	/**
	 * Get all fundraiser IDs in the subtree rooted at $parent_id (inclusive).
	 *
	 * @param int $parent_id Root fundraiser 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;
	}
}
